From 922bdebdac0a6804ac6ed629ddede20ccad830e2 Mon Sep 17 00:00:00 2001 From: Travis Semple Date: Thu, 18 Jul 2024 11:04:14 -0700 Subject: [PATCH 01/54] Fix filename off by 1 ms bug --- jobs/payment-jobs/tasks/ap_task.py | 6 +++--- jobs/payment-jobs/tasks/common/cgi_ejv.py | 5 +++-- jobs/payment-jobs/tasks/ejv_partner_distribution_task.py | 8 ++------ jobs/payment-jobs/tasks/ejv_payment_task.py | 4 ++-- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/jobs/payment-jobs/tasks/ap_task.py b/jobs/payment-jobs/tasks/ap_task.py index c317cb447..6290b10d4 100644 --- a/jobs/payment-jobs/tasks/ap_task.py +++ b/jobs/payment-jobs/tasks/ap_task.py @@ -167,10 +167,10 @@ def _create_non_gov_disbursement_file(cls): # pylint:disable=too-many-locals @classmethod def _create_file_and_upload(cls, ap_content): - file_path_with_name, trg_file_path = cls.create_inbox_and_trg_files(ap_content) - cls.upload(ap_content, cls.get_file_name(), file_path_with_name, trg_file_path) + file_path_with_name, trg_file_path, file_name = cls.create_inbox_and_trg_files(ap_content) + cls.upload(ap_content, file_name, file_path_with_name, trg_file_path) db.session.commit() - # Add a sleep to prevent collision on file name. + # Sleep to prevent collision on file name. time.sleep(1) @classmethod diff --git a/jobs/payment-jobs/tasks/common/cgi_ejv.py b/jobs/payment-jobs/tasks/common/cgi_ejv.py index 2993cf9ce..aa6b0a58b 100644 --- a/jobs/payment-jobs/tasks/common/cgi_ejv.py +++ b/jobs/payment-jobs/tasks/common/cgi_ejv.py @@ -148,7 +148,8 @@ def get_trg_suffix(cls): def create_inbox_and_trg_files(cls, ejv_content): """Create inbox and trigger files.""" file_path: str = tempfile.gettempdir() - file_path_with_name = f'{file_path}/{cls.get_file_name()}' + file_name = cls.get_file_name() + file_path_with_name = f'{file_path}/{file_name}' trg_file_path = f'{file_path_with_name}.{cls.get_trg_suffix()}' with open(file_path_with_name, 'a+', encoding='utf-8') as jv_file: jv_file.write(ejv_content) @@ -157,4 +158,4 @@ def create_inbox_and_trg_files(cls, ejv_content): with open(trg_file_path, 'a+', encoding='utf-8') as trg_file: trg_file.write('') trg_file.close() - return file_path_with_name, trg_file_path + return file_path_with_name, trg_file_path, file_name diff --git a/jobs/payment-jobs/tasks/ejv_partner_distribution_task.py b/jobs/payment-jobs/tasks/ejv_partner_distribution_task.py index 664826f49..c32304b01 100644 --- a/jobs/payment-jobs/tasks/ejv_partner_distribution_task.py +++ b/jobs/payment-jobs/tasks/ejv_partner_distribution_task.py @@ -53,7 +53,6 @@ def create_ejv_file(cls): cls._create_ejv_file_for_partner(batch_type='GI') # Internal ministry cls._create_ejv_file_for_partner(batch_type='GA') # External ministry - # TODO grab EFT invoices from PartnerDisbursements, eventually everything will run through partner disbursements. @staticmethod def get_invoices_for_disbursement(partner): """Return invoices for disbursement. Used by EJV and AP.""" @@ -276,11 +275,8 @@ def _create_ejv_file_for_partner(cls, batch_type: str): # pylint:disable=too-ma jv_batch_trailer: str = cls.get_batch_trailer(batch_number, batch_total, batch_type, control_total) ejv_content = f'{batch_header}{ejv_content}{jv_batch_trailer}' - # Create a file add this content. - file_path_with_name, trg_file_path = cls.create_inbox_and_trg_files(ejv_content) - - # Upload file and trg to FTP - cls.upload(ejv_content, cls.get_file_name(), file_path_with_name, trg_file_path) + file_path_with_name, trg_file_path, file_name = cls.create_inbox_and_trg_files(ejv_content) + cls.upload(ejv_content, file_name, file_path_with_name, trg_file_path) # commit changes to DB db.session.commit() diff --git a/jobs/payment-jobs/tasks/ejv_payment_task.py b/jobs/payment-jobs/tasks/ejv_payment_task.py index 22ae8a172..83b591b4d 100644 --- a/jobs/payment-jobs/tasks/ejv_payment_task.py +++ b/jobs/payment-jobs/tasks/ejv_payment_task.py @@ -209,12 +209,12 @@ def _create_ejv_file_for_gov_account(cls, batch_type: str): # pylint:disable=to ejv_content = f'{batch_header}{ejv_content}{batch_trailer}' # Create a file add this content. - file_path_with_name, trg_file_path = cls.create_inbox_and_trg_files(ejv_content) + file_path_with_name, trg_file_path, file_name = cls.create_inbox_and_trg_files(ejv_content) current_app.logger.info('Uploading to ftp.') # Upload file and trg to FTP - cls.upload(ejv_content, cls.get_file_name(), file_path_with_name, trg_file_path) + cls.upload(ejv_content, file_name, file_path_with_name, trg_file_path) # commit changes to DB db.session.commit() From c3fa97632b65a732aef246d54b8b15e6dd636905 Mon Sep 17 00:00:00 2001 From: Travis Semple Date: Fri, 6 Sep 2024 14:54:21 -0700 Subject: [PATCH 02/54] 21519 - Work on partner disbursements --- .github/CODEOWNERS | 2 +- .github/workflows/bcol-api-cd.yml | 1 + .github/workflows/bcol-api-ci.yml | 7 +- .github/workflows/ftp-poller-ci.yml | 7 +- .github/workflows/notebook-report-ci.yml | 1 + .github/workflows/pay-admin-ci.yml | 2 +- .github/workflows/pay-api-ci.yml | 7 +- .github/workflows/pay-queue-ci.yml | 7 +- .github/workflows/payment-jobs-ci.yml | 8 +- DEVELOPER_NOTES.md | 25 +- bcol-api/Dockerfile | 1 + bcol-api/Makefile | 1 + bcol-api/poetry.lock | 730 ++++++++------- bcol-api/pyproject.toml | 8 +- bcol-api/setup.cfg | 2 +- .../src/bcol_api/resources/bcol_payment.py | 1 + bcol-api/src/bcol_api/resources/ops.py | 1 - bcol-api/src/bcol_api/version.py | 2 +- docs/docs/api_contract/pay-api.yaml | 5 +- docs/docs/architecture/Pay Flow.png | Bin 131750 -> 46523 bytes jobs/ftp-poller/Dockerfile | 1 + jobs/ftp-poller/Makefile | 1 + jobs/ftp-poller/config.py | 12 +- jobs/ftp-poller/poetry.lock | 648 +++++++++++--- jobs/ftp-poller/pyproject.toml | 3 +- jobs/ftp-poller/setup.cfg | 2 +- jobs/notebook-report/requirements.txt | 2 +- jobs/notebook-report/setup.cfg | 2 +- jobs/payment-jobs/Dockerfile | 1 + jobs/payment-jobs/Makefile | 2 +- jobs/payment-jobs/config.py | 12 +- jobs/payment-jobs/invoke_jobs.py | 8 +- jobs/payment-jobs/poetry.lock | 843 +++++++++--------- jobs/payment-jobs/pyproject.toml | 4 +- jobs/payment-jobs/services/routing_slip.py | 2 - jobs/payment-jobs/setup.cfg | 2 +- .../tasks/activate_pad_account_task.py | 5 +- jobs/payment-jobs/tasks/ap_task.py | 53 +- .../tasks/bcol_refund_confirmation_task.py | 4 +- .../tasks/cfs_create_account_task.py | 4 +- .../tasks/cfs_create_invoice_task.py | 155 ++-- .../tasks/cfs_payment_method_updater.py | 147 --- jobs/payment-jobs/tasks/common/cgi_ap.py | 10 +- jobs/payment-jobs/tasks/common/cgi_ejv.py | 10 +- jobs/payment-jobs/tasks/common/dataclasses.py | 25 + .../tasks/direct_pay_automated_refund_task.py | 8 +- jobs/payment-jobs/tasks/distribution_task.py | 4 +- jobs/payment-jobs/tasks/eft_task.py | 341 +++++-- .../tasks/ejv_partner_distribution_task.py | 408 ++++----- jobs/payment-jobs/tasks/ejv_payment_task.py | 49 +- jobs/payment-jobs/tasks/routing_slip_task.py | 8 +- jobs/payment-jobs/tasks/stale_payment_task.py | 36 +- jobs/payment-jobs/tasks/statement_due_task.py | 168 +++- .../tasks/statement_notification_task.py | 12 +- jobs/payment-jobs/tasks/statement_task.py | 151 +++- .../tasks/unpaid_invoice_notify_task.py | 4 +- jobs/payment-jobs/tests/jobs/conftest.py | 30 + jobs/payment-jobs/tests/jobs/factory.py | 140 ++- .../jobs/test_activate_pad_account_task.py | 6 +- .../jobs/test_cfs_create_invoice_task.py | 46 +- .../test_direct_pay_automated_refund_task.py | 6 +- jobs/payment-jobs/tests/jobs/test_eft_task.py | 292 +++++- .../test_ejv_partner_distribution_task.py | 11 +- ...artner_partial_refund_distribution_task.py | 6 +- .../tests/jobs/test_generate_statements.py | 280 ------ .../tests/jobs/test_statement_due_task.py | 124 ++- .../jobs/test_statement_notification_task.py | 12 +- .../tests/jobs/test_statements_task.py | 497 +++++++++++ .../jobs/test_unpaid_invoice_notifytask.py | 43 +- jobs/payment-jobs/utils/auth_event.py | 36 +- jobs/payment-jobs/utils/mailer.py | 9 +- pay-admin/Dockerfile | 1 + pay-admin/Makefile | 1 + pay-admin/admin/version.py | 2 +- pay-admin/admin/views/distribution_code.py | 9 +- pay-admin/devops/vaults.json | 15 + pay-admin/logging.conf | 6 + pay-admin/poetry.lock | 678 +++++++------- pay-admin/pyproject.toml | 7 +- pay-admin/setup.cfg | 2 +- pay-admin/wsgi.py | 2 +- pay-api/Dockerfile | 3 +- pay-api/Makefile | 2 +- pay-api/gunicorn_config.py | 4 +- pay-api/migrations/README.md | 2 +- pay-api/migrations/alembic.ini | 4 +- pay-api/migrations/env.py | 16 +- pay-api/migrations/script.py.mako | 4 + .../00467a306afd_bcorp_filing_types.py | 30 +- .../06f6e75c18d8_vital_statistics_fee_code.py | 8 +- .../0aa7d4deef8a_nofee_filing_type.py | 10 +- ...921d5e32835_add_expense_authority_email.py | 2 +- .../versions/2024_07_22_f9c15c7f29f5_.py | 46 + .../versions/2024_07_25_4e57f6cf649c_.py | 93 ++ ..._1d5b66ef7f81_overdue_notification_date.py | 30 + ...e64c153e63ae_eft_short_names_historical.py | 76 ++ ...07_d197b43e25dc_eft_created_to_approved.py | 27 + .../versions/2024_08_08_17ca5cd561ca_.py | 38 + .../versions/2024_08_08_5cb9c5f5896c_.py | 67 ++ ...0b7fc6437_eft_invoice_refund_historical.py | 34 + .../versions/2024_08_16_fc32e7db4493_.py | 43 + .../versions/2024_08_29_2097573390f1_.py | 36 + .../versions/2024_09_06_aae01971bd53_.py | 64 ++ .../versions/2efd8e2b92a1_ppr_fee_codes.py | 12 +- .../versions/44bd57ece7b0_ppr_filing_types.py | 8 +- ...3_ppr_and_dissolution_filing_type_codes.py | 70 +- .../5cdd1c1d355e_correction_fee_codes.py | 6 +- .../5d9997f7e649_variable_fee_code.py | 10 +- .../5f7df60469fa_adding_ppr_metadata.py | 8 +- .../migrations/versions/609b98d87a72_rpt.py | 4 +- .../migrations/versions/643790dd3334_cso.py | 4 +- pay-api/migrations/versions/6a6b042b831a_.py | 8 +- .../6f0fe9f23d8c_distribution_code_changes.py | 6 +- .../7ea7ba8fe991_alteration_filing.py | 4 +- .../9c8a93ba9da2_restricted_ppr_fee_codes.py | 4 +- .../a11be9fe1a6a_distribution_code.py | 22 +- .../b336780735dc_registration_filing_type.py | 6 +- .../versions/b7443d501d98_entity_fee_codes.py | 8 +- .../bae02665e807_adding_zero_dollar_data.py | 4 +- .../c67213f860ea_incorporation_fee_codes.py | 10 +- .../versions/c871202927f0_rush_fee_code.py | 4 +- .../daa392b64cb7_fee_master_inserts.py | 24 +- .../versions/dbe9dc38ac33_firms_fee_codes.py | 6 +- .../versions/e699674b1774_nsf_fee_code.py | 4 +- .../e6eb14b9d50e_fee_master_for_bcorps.py | 26 +- pay-api/migrations/versions/e748b5c19247_.py | 12 +- .../versions/e8edc889072d_nr_fee_codes.py | 6 +- pay-api/poetry.lock | 674 +++++++------- pay-api/pre-hook-update-db.sh | 4 + pay-api/pyproject.toml | 21 +- pay-api/src/pay_api/__init__.py | 18 +- pay-api/src/pay_api/config.py | 2 + .../pay_api/factory/payment_system_factory.py | 3 - pay-api/src/pay_api/models/__init__.py | 7 +- pay-api/src/pay_api/models/audit.py | 6 +- pay-api/src/pay_api/models/cas_settlement.py | 4 +- pay-api/src/pay_api/models/comment.py | 4 +- pay-api/src/pay_api/models/corp_type.py | 2 + pay-api/src/pay_api/models/credit.py | 4 +- pay-api/src/pay_api/models/custom_query.py | 8 + .../src/pay_api/models/distribution_code.py | 10 +- pay-api/src/pay_api/models/eft_credit.py | 5 +- .../pay_api/models/eft_credit_invoice_link.py | 19 +- pay-api/src/pay_api/models/eft_file.py | 4 +- pay-api/src/pay_api/models/eft_refund.py | 6 +- .../pay_api/models/eft_refund_email_list.py | 8 +- .../pay_api/models/eft_short_name_links.py | 21 +- pay-api/src/pay_api/models/eft_short_names.py | 4 +- .../models/eft_short_names_historical.py | 116 +++ pay-api/src/pay_api/models/eft_transaction.py | 7 +- pay-api/src/pay_api/models/ejv_file.py | 4 +- pay-api/src/pay_api/models/fee_schedule.py | 9 +- pay-api/src/pay_api/models/invoice.py | 51 +- .../src/pay_api/models/invoice_reference.py | 35 +- .../pay_api/models/non_sufficient_funds.py | 9 +- .../pay_api/models/partner_disbursements.py | 8 +- pay-api/src/pay_api/models/payment.py | 30 +- pay-api/src/pay_api/models/payment_account.py | 8 +- .../src/pay_api/models/payment_line_item.py | 9 +- .../src/pay_api/models/payment_transaction.py | 6 +- pay-api/src/pay_api/models/refunds_partial.py | 4 - pay-api/src/pay_api/models/statement.py | 51 +- .../src/pay_api/models/statement_invoices.py | 2 +- .../src/pay_api/models/statement_settings.py | 22 +- pay-api/src/pay_api/resources/ops.py | 5 +- pay-api/src/pay_api/resources/v1/account.py | 4 +- .../pay_api/resources/v1/eft_short_names.py | 23 +- pay-api/src/pay_api/resources/v1/fee.py | 4 +- pay-api/src/pay_api/resources/v1/payment.py | 9 +- pay-api/src/pay_api/services/__init__.py | 6 +- pay-api/src/pay_api/services/auth.py | 10 + .../pay_api/services/base_payment_system.py | 19 +- pay-api/src/pay_api/services/bcol_service.py | 4 +- pay-api/src/pay_api/services/cfs_service.py | 21 +- .../pay_api/services/direct_pay_service.py | 45 +- .../src/pay_api/services/distribution_code.py | 4 +- pay-api/src/pay_api/services/eft_service.py | 152 +++- .../services/eft_short_name_historical.py | 216 +++++ .../src/pay_api/services/eft_short_names.py | 383 ++++++-- .../src/pay_api/services/eft_transactions.py | 125 --- pay-api/src/pay_api/services/email_service.py | 35 + .../src/pay_api/services/fas/routing_slip.py | 4 +- pay-api/src/pay_api/services/fee_schedule.py | 1 - .../pay_api/services/gcp_queue_publisher.py | 3 + .../pay_api/services/internal_pay_service.py | 4 +- pay-api/src/pay_api/services/invoice.py | 5 + .../pay_api/services/non_sufficient_funds.py | 46 +- pay-api/src/pay_api/services/payment.py | 124 +-- .../src/pay_api/services/payment_account.py | 143 +-- .../src/pay_api/services/payment_service.py | 2 +- .../pay_api/services/payment_transaction.py | 29 +- pay-api/src/pay_api/services/receipt.py | 19 +- pay-api/src/pay_api/services/refund.py | 6 +- pay-api/src/pay_api/services/statement.py | 184 ++-- .../pay_api/services/statement_recipients.py | 2 +- .../pay_api/services/statement_settings.py | 33 +- pay-api/src/pay_api/services/wire_service.py | 26 - .../templates/eft_refund_notification.html | 2 +- .../templates/eft_reverse_payment.html | 13 + pay-api/src/pay_api/utils/enums.py | 22 +- pay-api/src/pay_api/utils/errors.py | 9 + pay-api/src/pay_api/utils/util.py | 16 +- pay-api/src/pay_api/version.py | 2 +- pay-api/tests/conftest.py | 17 +- .../tests/unit/api/fas/test_routing_slip.py | 15 +- pay-api/tests/unit/api/test_account.py | 18 +- pay-api/tests/unit/api/test_cors_preflight.py | 2 +- .../unit/api/test_eft_payment_actions.py | 118 ++- .../unit/api/test_eft_short_name_history.py | 198 ++++ .../tests/unit/api/test_eft_short_names.py | 66 +- .../tests/unit/api/test_eft_transactions.py | 280 ------ pay-api/tests/unit/api/test_fee.py | 12 +- pay-api/tests/unit/api/test_payment.py | 69 +- .../tests/unit/api/test_payment_request.py | 2 +- pay-api/tests/unit/api/test_receipt.py | 4 +- pay-api/tests/unit/api/test_refund.py | 90 +- pay-api/tests/unit/api/test_statement.py | 55 +- .../tests/unit/api/test_statement_settings.py | 4 +- .../factory/test_payment_system_factory.py | 11 +- .../models/test_eft_credit_invoice_link.py | 3 + pay-api/tests/unit/models/test_eft_credits.py | 20 +- .../unit/models/test_eft_short_name_links.py | 10 +- .../tests/unit/models/test_eft_short_names.py | 4 +- .../models/test_eft_short_names_historical.py | 89 ++ .../tests/unit/models/test_fee_schedule.py | 19 +- pay-api/tests/unit/models/test_invoice.py | 48 +- .../unit/models/test_payment_transaction.py | 16 +- pay-api/tests/unit/models/test_receipt.py | 6 +- .../tests/unit/services/test_cfs_service.py | 2 +- .../unit/services/test_direct_pay_service.py | 13 +- .../unit/services/test_distribution_code.py | 5 +- .../tests/unit/services/test_eft_service.py | 195 +++- .../test_eft_short_name_historical.py | 126 +++ .../tests/unit/services/test_fee_schedule.py | 33 +- pay-api/tests/unit/services/test_invoice.py | 27 +- .../services/test_non_sufficient_funds.py | 14 +- .../tests/unit/services/test_pad_service.py | 9 +- pay-api/tests/unit/services/test_payment.py | 14 +- .../unit/services/test_payment_account.py | 2 +- .../unit/services/test_payment_service.py | 27 +- .../unit/services/test_payment_transaction.py | 68 +- pay-api/tests/unit/services/test_receipt.py | 4 +- pay-api/tests/unit/services/test_refund.py | 4 +- .../services/test_routing_slip_service.py | 18 +- pay-api/tests/unit/services/test_statement.py | 165 +--- .../unit/services/test_statement_settings.py | 51 +- .../tests/unit/services/test_wire_service.py | 35 - pay-api/tests/utilities/base_test.py | 58 +- pay-queue/Dockerfile | 1 + pay-queue/Makefile | 2 +- pay-queue/devops/vaults.json | 6 +- pay-queue/poetry.lock | 708 ++++++++------- pay-queue/pyproject.toml | 3 +- pay-queue/setup.cfg | 2 +- pay-queue/src/pay_queue/resources/worker.py | 2 +- .../pay_queue/services/cgi_reconciliations.py | 35 +- .../services/eft/eft_reconciliation.py | 211 ++--- .../src/pay_queue/services/email_service.py | 96 ++ .../services/payment_reconciliations.py | 109 +-- pay-queue/tests/integration/factory.py | 40 +- .../integration/test_cgi_reconciliations.py | 44 +- .../integration/test_eft_reconciliation.py | 270 +++--- .../test_payment_reconciliations.py | 21 +- 263 files changed, 8620 insertions(+), 5082 deletions(-) delete mode 100644 jobs/payment-jobs/tasks/cfs_payment_method_updater.py delete mode 100644 jobs/payment-jobs/tests/jobs/test_generate_statements.py create mode 100644 jobs/payment-jobs/tests/jobs/test_statements_task.py create mode 100644 pay-admin/devops/vaults.json create mode 100644 pay-api/migrations/versions/2024_07_22_f9c15c7f29f5_.py create mode 100644 pay-api/migrations/versions/2024_07_25_4e57f6cf649c_.py create mode 100644 pay-api/migrations/versions/2024_07_30_1d5b66ef7f81_overdue_notification_date.py create mode 100644 pay-api/migrations/versions/2024_08_06_e64c153e63ae_eft_short_names_historical.py create mode 100644 pay-api/migrations/versions/2024_08_07_d197b43e25dc_eft_created_to_approved.py create mode 100644 pay-api/migrations/versions/2024_08_08_17ca5cd561ca_.py create mode 100644 pay-api/migrations/versions/2024_08_08_5cb9c5f5896c_.py create mode 100644 pay-api/migrations/versions/2024_08_14_4410b7fc6437_eft_invoice_refund_historical.py create mode 100644 pay-api/migrations/versions/2024_08_16_fc32e7db4493_.py create mode 100644 pay-api/migrations/versions/2024_08_29_2097573390f1_.py create mode 100644 pay-api/migrations/versions/2024_09_06_aae01971bd53_.py create mode 100644 pay-api/pre-hook-update-db.sh create mode 100644 pay-api/src/pay_api/models/eft_short_names_historical.py create mode 100644 pay-api/src/pay_api/services/eft_short_name_historical.py delete mode 100644 pay-api/src/pay_api/services/eft_transactions.py delete mode 100644 pay-api/src/pay_api/services/wire_service.py create mode 100644 pay-api/src/pay_api/templates/eft_reverse_payment.html create mode 100644 pay-api/tests/unit/api/test_eft_short_name_history.py delete mode 100755 pay-api/tests/unit/api/test_eft_transactions.py create mode 100644 pay-api/tests/unit/models/test_eft_short_names_historical.py create mode 100644 pay-api/tests/unit/services/test_eft_short_name_historical.py delete mode 100644 pay-api/tests/unit/services/test_wire_service.py create mode 100644 pay-queue/src/pay_queue/services/email_service.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cba640665..d085e59b9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @seeker25 @jxio @ochiu @rodrigo-barraza @avni-work @sameer0422 +* @seeker25 @jxio @ochiu @rodrigo-barraza diff --git a/.github/workflows/bcol-api-cd.yml b/.github/workflows/bcol-api-cd.yml index ca8bc50c4..eb421e0ed 100644 --- a/.github/workflows/bcol-api-cd.yml +++ b/.github/workflows/bcol-api-cd.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - release/* paths: - "bcol-api/**" workflow_dispatch: diff --git a/.github/workflows/bcol-api-ci.yml b/.github/workflows/bcol-api-ci.yml index bb360b2f9..d2ae92bed 100644 --- a/.github/workflows/bcol-api-ci.yml +++ b/.github/workflows/bcol-api-ci.yml @@ -4,7 +4,7 @@ on: pull_request: branches: - main - - feature-queue-python-upgrade + - release/* paths: - "bcol-api/**" @@ -102,6 +102,11 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Install docker-compose + run: | + sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + docker-compose version - name: Install dependencies run: | sudo apt update diff --git a/.github/workflows/ftp-poller-ci.yml b/.github/workflows/ftp-poller-ci.yml index c2c543dbf..d314fda73 100644 --- a/.github/workflows/ftp-poller-ci.yml +++ b/.github/workflows/ftp-poller-ci.yml @@ -4,7 +4,7 @@ on: pull_request: branches: - main - - feature-queue-python-upgrade + - release/* paths: - "jobs/ftp-poller/**" @@ -82,6 +82,11 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Install docker-compose + run: | + sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + docker-compose version - name: Install dependencies run: | make setup diff --git a/.github/workflows/notebook-report-ci.yml b/.github/workflows/notebook-report-ci.yml index d06d27337..4fb3ee3f6 100644 --- a/.github/workflows/notebook-report-ci.yml +++ b/.github/workflows/notebook-report-ci.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - main + - release/* paths: - "jobs/notebook-report/**" diff --git a/.github/workflows/pay-admin-ci.yml b/.github/workflows/pay-admin-ci.yml index bc2cdd2cc..7741d589a 100644 --- a/.github/workflows/pay-admin-ci.yml +++ b/.github/workflows/pay-admin-ci.yml @@ -4,7 +4,7 @@ on: pull_request: branches: - main - - feature-queue-python-upgrade + - release/* paths: - "pay-admin/**" - "pay-api/src/pay_api/models/**" diff --git a/.github/workflows/pay-api-ci.yml b/.github/workflows/pay-api-ci.yml index 0983f7146..3d9971eaa 100644 --- a/.github/workflows/pay-api-ci.yml +++ b/.github/workflows/pay-api-ci.yml @@ -4,7 +4,7 @@ on: pull_request: branches: - main - - feature-queue-python-upgrade + - release/* paths: - "pay-api/**" @@ -93,6 +93,11 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Install docker-compose + run: | + sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + docker-compose version - name: Install dependencies run: | make setup diff --git a/.github/workflows/pay-queue-ci.yml b/.github/workflows/pay-queue-ci.yml index 7026c89bd..e438f0dc9 100644 --- a/.github/workflows/pay-queue-ci.yml +++ b/.github/workflows/pay-queue-ci.yml @@ -4,7 +4,7 @@ on: pull_request: branches: - main - - feature-queue-python-upgrade + - release/* paths: - "pay-queue/**" - "pay-api/src/pay_api/models/**" @@ -93,6 +93,11 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Install docker-compose + run: | + sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + docker-compose version - name: Install dependencies run: | make setup diff --git a/.github/workflows/payment-jobs-ci.yml b/.github/workflows/payment-jobs-ci.yml index d002cbc3a..39a76aeff 100644 --- a/.github/workflows/payment-jobs-ci.yml +++ b/.github/workflows/payment-jobs-ci.yml @@ -4,8 +4,7 @@ on: pull_request: branches: - main - - feature-queue-python-upgrade - - release* + - release/* paths: - "jobs/payment-jobs/**" - "pay-api/src/pay_api/models/**" @@ -85,6 +84,11 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Install docker-compose + run: | + sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + docker-compose version - name: Install dependencies run: | make setup diff --git a/DEVELOPER_NOTES.md b/DEVELOPER_NOTES.md index b4e10d62e..d8ba472bf 100644 --- a/DEVELOPER_NOTES.md +++ b/DEVELOPER_NOTES.md @@ -190,4 +190,27 @@ it will work and process on the pod. 21. Where are the reports generated (report-api)? Here: https://github.com/bcgov/bcros-common/ - +22. How do I resolve some of the database performance issues? Take a look at some of the longer running queries if they're stuck: + +SELECT pid, usename, query, state, + EXTRACT(EPOCH FROM (now() - query_start)) AS duration +FROM pg_stat_activity +WHERE state != 'idle' +ORDER BY duration DESC; + +or + +SELECT * +FROM pg_stat_activity +WHERE state = 'active'; + +Terminate long running queries if required, for long running query operations, if it is a parallel worker you should kill the leader_pid as well or it can just spawn more parallel workers: + +SELECT pg_terminate_backend(pid) +FROM pg_stat_activity +WHERE pid in (146105, + 146355, + 146394 + ); + + \ No newline at end of file diff --git a/bcol-api/Dockerfile b/bcol-api/Dockerfile index 552e2e7d9..d48b75ab0 100644 --- a/bcol-api/Dockerfile +++ b/bcol-api/Dockerfile @@ -77,6 +77,7 @@ COPY --chown=web:web ./README.md /code # Project initialization: RUN --mount=type=cache,target="$POETRY_CACHE_DIR" \ echo "$APP_ENV" \ + && poetry config installer.max-workers 1 \ && poetry version \ # Install deps: && poetry run pip install -U pip \ diff --git a/bcol-api/Makefile b/bcol-api/Makefile index 0337b26fa..4129c5077 100755 --- a/bcol-api/Makefile +++ b/bcol-api/Makefile @@ -39,6 +39,7 @@ clean-test: ## clean test files install: clean unset HOME ## unset HOME because it's in the DEV .env file, will cause permissions issues pip install poetry ;\ + poetry config installer.max-workers 1 poetry install ################################################################################# diff --git a/bcol-api/poetry.lock b/bcol-api/poetry.lock index 879c50fae..33749ca13 100644 --- a/bcol-api/poetry.lock +++ b/bcol-api/poetry.lock @@ -16,13 +16,13 @@ dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] [[package]] name = "astroid" -version = "3.1.0" +version = "3.2.4" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.8.0" files = [ - {file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"}, - {file = "astroid-3.1.0.tar.gz", hash = "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"}, + {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"}, + {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, ] [[package]] @@ -46,49 +46,49 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "autopep8" -version = "2.0.4" +version = "2.3.1" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "autopep8-2.0.4-py2.py3-none-any.whl", hash = "sha256:067959ca4a07b24dbd5345efa8325f5f58da4298dab0dde0443d5ed765de80cb"}, - {file = "autopep8-2.0.4.tar.gz", hash = "sha256:2913064abd97b3419d1cc83ea71f042cb821f87e45b9c88cad5ad3c4ea87fe0c"}, + {file = "autopep8-2.3.1-py2.py3-none-any.whl", hash = "sha256:a203fe0fcad7939987422140ab17a930f684763bf7335bdb6709991dd7ef6c2d"}, + {file = "autopep8-2.3.1.tar.gz", hash = "sha256:8d6c87eba648fdcfc83e29b788910b8643171c395d9c4bcf115ece035b9c9dda"}, ] [package.dependencies] -pycodestyle = ">=2.10.0" +pycodestyle = ">=2.12.0" [[package]] name = "blinker" -version = "1.7.0" +version = "1.8.2" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.8" files = [ - {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, - {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, + {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, + {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, ] [[package]] name = "cachelib" -version = "0.12.0" +version = "0.13.0" description = "A collection of cache libraries in the same API interface." optional = false python-versions = ">=3.8" files = [ - {file = "cachelib-0.12.0-py3-none-any.whl", hash = "sha256:038f4d855afc3eb8caab10458f6eac55c328911f9055824c22c2f259ef9ed3a3"}, - {file = "cachelib-0.12.0.tar.gz", hash = "sha256:8243029a028436fd23229113dee517c0700bb43a8a289ec5a963e4af9ca2b194"}, + {file = "cachelib-0.13.0-py3-none-any.whl", hash = "sha256:8c8019e53b6302967d4e8329a504acf75e7bc46130291d30188a6e4e58162516"}, + {file = "cachelib-0.13.0.tar.gz", hash = "sha256:209d8996e3c57595bee274ff97116d1d73c4980b2fd9a34c7846cd07fd2e1a48"}, ] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.8.30" 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"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -217,63 +217,83 @@ files = [ [[package]] name = "coverage" -version = "7.4.3" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, - {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, - {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, - {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, - {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, - {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, - {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, - {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, - {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, - {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, - {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, - {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, - {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, - {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, - {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, - {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, - {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, - {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, - {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, - {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.extras] @@ -296,13 +316,13 @@ profile = ["gprof2dot (>=2022.7.29)"] [[package]] name = "ecdsa" -version = "0.18.0" +version = "0.19.0" description = "ECDSA cryptographic signature library (pure python)" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6" files = [ - {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, - {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, + {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"}, + {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"}, ] [package.dependencies] @@ -314,18 +334,18 @@ gmpy2 = ["gmpy2"] [[package]] name = "flake8" -version = "7.0.0" +version = "7.1.1" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" files = [ - {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, - {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, + {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, + {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.11.0,<2.12.0" +pycodestyle = ">=2.12.0,<2.13.0" pyflakes = ">=3.2.0,<3.3.0" [[package]] @@ -402,13 +422,13 @@ setuptools = "*" [[package]] name = "flask" -version = "3.0.2" +version = "3.0.3" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.8" files = [ - {file = "flask-3.0.2-py3-none-any.whl", hash = "sha256:3232e0e9c850d781933cf0207523d1ece087eb8d87b23777ae38456e2fbe7c6e"}, - {file = "flask-3.0.2.tar.gz", hash = "sha256:822c03f4b799204250a7ee84b1eddc40665395333973dfb9deebfe425fefcb7d"}, + {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, + {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, ] [package.dependencies] @@ -423,41 +443,44 @@ async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] [[package]] -name = "flask_jwt_oidc" -version = "0.3.0" -description = "Flask JWT OIDC" +name = "flask-jwt-oidc" +version = "0.7.0" +description = "Opinionated flask oidc client" optional = false -python-versions = "*" +python-versions = "^3.9" files = [] develop = false [package.dependencies] -cachelib = "*" -flask = "*" -python-jose = "*" -six = "*" +cachelib = "^0.13.0" +Flask = ">=2" +python-jose = "^3.3.0" +six = "^1.16.0" [package.source] type = "git" url = "https://github.com/thorwolpert/flask-jwt-oidc.git" reference = "HEAD" -resolved_reference = "40cc811ccf70e838c5f7522fe8d83b7e58853539" +resolved_reference = "a3e67443615663ce1dbf22cc55741172fa56b063" [[package]] name = "flask-moment" -version = "1.0.5" +version = "1.0.6" description = "Formatting of dates and times in Flask templates using moment.js." optional = false python-versions = ">=3.6" files = [ - {file = "Flask-Moment-1.0.5.tar.gz", hash = "sha256:33307ecd4af8290b6df6a9828ff55ac0977d0567817f9bc0f69803d22ed2b55c"}, - {file = "Flask_Moment-1.0.5-py3-none-any.whl", hash = "sha256:6e7b3eef89e2137bbbee975405f241a68a44edfa34bf052c92d84364992adca6"}, + {file = "Flask_Moment-1.0.6-py3-none-any.whl", hash = "sha256:3ae8baea20a41e99f457b9710ecd1368911dd5133f09a27583eb0dcb3491e31d"}, + {file = "flask_moment-1.0.6.tar.gz", hash = "sha256:2f8969907cbacde4a88319792e8f920ba5c9dd9d99ced2346cad563795302b88"}, ] [package.dependencies] Flask = "*" packaging = ">=14.1" +[package.extras] +docs = ["sphinx"] + [[package]] name = "flask-opentracing" version = "1.1.0" @@ -600,49 +623,54 @@ test = ["objgraph", "psutil"] [[package]] name = "gunicorn" -version = "21.2.0" +version = "22.0.0" description = "WSGI HTTP Server for UNIX" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, - {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, + {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"}, + {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"}, ] [package.dependencies] packaging = "*" [package.extras] -eventlet = ["eventlet (>=0.24.1)"] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] gevent = ["gevent (>=1.4.0)"] setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] tornado = ["tornado (>=0.2)"] [[package]] name = "idna" -version = "3.6" +version = "3.8" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, + {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, ] [[package]] name = "importlib-resources" -version = "6.3.0" +version = "6.4.4" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_resources-6.3.0-py3-none-any.whl", hash = "sha256:783407aa1cd05550e3aa123e8f7cfaebee35ffa9cb0242919e2d1e4172222705"}, - {file = "importlib_resources-6.3.0.tar.gz", hash = "sha256:166072a97e86917a9025876f34286f549b9caf1d10b35a1b372bffa1600c6569"}, + {file = "importlib_resources-6.4.4-py3-none-any.whl", hash = "sha256:dda242603d1c9cd836c3368b1174ed74cb4049ecd209e7a1a0104620c18c5c11"}, + {file = "importlib_resources-6.4.4.tar.gz", hash = "sha256:20600c8b7361938dc0bb2d5ec0297802e575df486f5a544fa414da65e13721f7"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.collections", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] [[package]] name = "iniconfig" @@ -685,13 +713,13 @@ colors = ["colorama (>=0.4.6)"] [[package]] name = "itsdangerous" -version = "2.1.2" +version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, - {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, ] [[package]] @@ -715,13 +743,13 @@ tests = ["codecov", "coverage", "flake8", "flake8-quotes", "flake8-typing-import [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -765,96 +793,157 @@ six = "*" [[package]] name = "lxml" -version = "5.1.0" +version = "5.3.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.6" files = [ - {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, - {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, - {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2befa20a13f1a75c751f47e00929fb3433d67eb9923c2c0b364de449121f447c"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b7ee4c35f374e2c20337a95502057964d7e35b996b1c667b5c65c567d2252a"}, - {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf8443781533b8d37b295016a4b53c1494fa9a03573c09ca5104550c138d5c05"}, - {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, - {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, - {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"}, - {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"}, - {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, - {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, - {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"}, - {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"}, - {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"}, - {file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"}, - {file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"}, - {file = "lxml-5.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:24ef5a4631c0b6cceaf2dbca21687e29725b7c4e171f33a8f8ce23c12558ded1"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d2900b7f5318bc7ad8631d3d40190b95ef2aa8cc59473b73b294e4a55e9f30f"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:601f4a75797d7a770daed8b42b97cd1bb1ba18bd51a9382077a6a247a12aa38d"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4b68c961b5cc402cbd99cca5eb2547e46ce77260eb705f4d117fd9c3f932b95"}, - {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:afd825e30f8d1f521713a5669b63657bcfe5980a916c95855060048b88e1adb7"}, - {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:262bc5f512a66b527d026518507e78c2f9c2bd9eb5c8aeeb9f0eb43fcb69dc67"}, - {file = "lxml-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:e856c1c7255c739434489ec9c8aa9cdf5179785d10ff20add308b5d673bed5cd"}, - {file = "lxml-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c7257171bb8d4432fe9d6fdde4d55fdbe663a63636a17f7f9aaba9bcb3153ad7"}, - {file = "lxml-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9e240ae0ba96477682aa87899d94ddec1cc7926f9df29b1dd57b39e797d5ab5"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a96f02ba1bcd330807fc060ed91d1f7a20853da6dd449e5da4b09bfcc08fdcf5"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3898ae2b58eeafedfe99e542a17859017d72d7f6a63de0f04f99c2cb125936"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c5a7edbd7c695e54fca029ceb351fc45cd8860119a0f83e48be44e1c464862"}, - {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3aeca824b38ca78d9ee2ab82bd9883083d0492d9d17df065ba3b94e88e4d7ee6"}, - {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, - {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, - {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, - {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98f3f020a2b736566c707c8e034945c02aa94e124c24f77ca097c446f81b01f1"}, - {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, - {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, - {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8b0c78e7aac24979ef09b7f50da871c2de2def043d468c4b41f512d831e912"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bcf86dfc8ff3e992fed847c077bd875d9e0ba2fa25d859c3a0f0f76f07f0c8d"}, - {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:49a9b4af45e8b925e1cd6f3b15bbba2c81e7dba6dce170c677c9cda547411e14"}, - {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:280f3edf15c2a967d923bcfb1f8f15337ad36f93525828b40a0f9d6c2ad24890"}, - {file = "lxml-5.1.0-cp39-cp39-win32.whl", hash = "sha256:ed7326563024b6e91fef6b6c7a1a2ff0a71b97793ac33dbbcf38f6005e51ff6e"}, - {file = "lxml-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d7b4beebb178e9183138f552238f7e6613162a42164233e2bda00cb3afac58f"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"}, - {file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"}, + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"}, + {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"}, + {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"}, + {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"}, + {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"}, + {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"}, + {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"}, + {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"}, + {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"}, + {file = "lxml-5.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237"}, + {file = "lxml-5.3.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577"}, + {file = "lxml-5.3.0-cp36-cp36m-win32.whl", hash = "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70"}, + {file = "lxml-5.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c"}, + {file = "lxml-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11"}, + {file = "lxml-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84"}, + {file = "lxml-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e"}, + {file = "lxml-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42"}, + {file = "lxml-5.3.0-cp38-cp38-win32.whl", hash = "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e"}, + {file = "lxml-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a"}, + {file = "lxml-5.3.0-cp39-cp39-win32.whl", hash = "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff"}, + {file = "lxml-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c"}, + {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.7)"] +source = ["Cython (>=3.0.11)"] [[package]] name = "markupsafe" @@ -951,13 +1040,13 @@ tests = ["Sphinx", "doubles", "flake8", "flake8-quotes", "gevent", "mock", "pyte [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -976,28 +1065,29 @@ flake8 = ">=5.0.0" [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -1087,38 +1177,38 @@ files = [ [[package]] name = "pyasn1" -version = "0.5.1" +version = "0.6.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.8" files = [ - {file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"}, - {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, + {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, + {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, ] [[package]] name = "pyasn1-modules" -version = "0.3.0" +version = "0.4.0" description = "A collection of ASN.1-based protocols modules" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.8" files = [ - {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, - {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, + {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, + {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, ] [package.dependencies] -pyasn1 = ">=0.4.6,<0.6.0" +pyasn1 = ">=0.4.6,<0.7.0" [[package]] name = "pycodestyle" -version = "2.11.1" +version = "2.12.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, - {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, ] [[package]] @@ -1179,17 +1269,17 @@ tests-numpy = ["numpy", "pyhamcrest[tests]"] [[package]] name = "pylint" -version = "3.1.0" +version = "3.2.7" description = "python code static checker" optional = false python-versions = ">=3.8.0" files = [ - {file = "pylint-3.1.0-py3-none-any.whl", hash = "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74"}, - {file = "pylint-3.1.0.tar.gz", hash = "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"}, + {file = "pylint-3.2.7-py3-none-any.whl", hash = "sha256:02f4aedeac91be69fb3b4bea997ce580a4ac68ce58b89eaefeaf06749df73f4b"}, + {file = "pylint-3.2.7.tar.gz", hash = "sha256:1b7a721b575eaeaa7d39db076b6e7743c993ea44f57979127c517c6c572c803e"}, ] [package.dependencies] -astroid = ">=3.1.0,<=3.2.0-dev0" +astroid = ">=3.2.4,<=3.3.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = {version = ">=0.3.7", markers = "python_version >= \"3.12\""} isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" @@ -1271,23 +1361,23 @@ files = [ [[package]] name = "pytest" -version = "8.1.1" +version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, - {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.4,<2.0" +pluggy = ">=1.5,<2" [package.extras] -testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" @@ -1309,17 +1399,17 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-mock" -version = "3.12.0" +version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"}, - {file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f"}, + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, ] [package.dependencies] -pytest = ">=5.0" +pytest = ">=6.2.5" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] @@ -1386,13 +1476,13 @@ files = [ [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -1407,13 +1497,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-file" -version = "2.0.0" +version = "2.1.0" description = "File transport adapter for Requests" optional = false python-versions = "*" files = [ - {file = "requests-file-2.0.0.tar.gz", hash = "sha256:20c5931629c558fda566cacc10cfe2cd502433e628f568c34c80d96a0cc95972"}, - {file = "requests_file-2.0.0-py2.py3-none-any.whl", hash = "sha256:3e493d390adb44aa102ebea827a48717336d5268968c370eaf19abaf5cae13bf"}, + {file = "requests_file-2.1.0-py2.py3-none-any.whl", hash = "sha256:cf270de5a4c5874e84599fc5778303d496c10ae5e870bfa378818f35d21bda5c"}, + {file = "requests_file-2.1.0.tar.gz", hash = "sha256:0f549a3f3b0699415ac04d167e9cb39bccfb730cb832b4d20be3d9867356e658"}, ] [package.dependencies] @@ -1467,18 +1557,18 @@ jaeger-client = "*" type = "git" url = "https://github.com/bcgov/sbc-common-components.git" reference = "HEAD" -resolved_reference = "5f99e135214ae949c9af951d4aa0b88b1067d853" +resolved_reference = "22978d810dc4e85c51c3129936686b0a17124e64" subdirectory = "python" [[package]] name = "sentry-sdk" -version = "1.42.0" +version = "2.13.0" description = "Python client for Sentry (https://sentry.io)" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "sentry-sdk-1.42.0.tar.gz", hash = "sha256:4a8364b8f7edbf47f95f7163e48334c96100d9c098f0ae6606e2e18183c223e6"}, - {file = "sentry_sdk-1.42.0-py2.py3-none-any.whl", hash = "sha256:a654ee7e497a3f5f6368b36d4f04baeab1fe92b3105f7f6965d6ef0de35a9ba4"}, + {file = "sentry_sdk-2.13.0-py2.py3-none-any.whl", hash = "sha256:6beede8fc2ab4043da7f69d95534e320944690680dd9a963178a49de71d726c6"}, + {file = "sentry_sdk-2.13.0.tar.gz", hash = "sha256:8d4a576f7a98eb2fdb40e13106e41f330e5c79d72a68be1316e7852cf4995260"}, ] [package.dependencies] @@ -1486,28 +1576,33 @@ blinker = {version = ">=1.1", optional = true, markers = "extra == \"flask\""} certifi = "*" flask = {version = ">=0.11", optional = true, markers = "extra == \"flask\""} markupsafe = {version = "*", optional = true, markers = "extra == \"flask\""} -urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} +urllib3 = ">=1.26.11" [package.extras] aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] arq = ["arq (>=0.23)"] asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] chalice = ["chalice (>=1.16.0)"] clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] -grpcio = ["grpcio (>=1.21.1)"] +grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] +huggingface-hub = ["huggingface-hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] +litestar = ["litestar (>=2.0.0)"] loguru = ["loguru (>=0.5)"] openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] -opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +opentelemetry-experimental = ["opentelemetry-distro"] pure-eval = ["asttokens", "executing", "pure-eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] @@ -1517,22 +1612,27 @@ sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] -tornado = ["tornado (>=5)"] +tornado = ["tornado (>=6)"] [[package]] name = "setuptools" -version = "70.0.0" +version = "74.1.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, - {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, + {file = "setuptools-74.1.2-py3-none-any.whl", hash = "sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308"}, + {file = "setuptools-74.1.2.tar.gz", hash = "sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "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] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.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-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] [[package]] name = "six" @@ -1558,64 +1658,64 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.28" +version = "2.0.34" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0b148ab0438f72ad21cb004ce3bdaafd28465c4276af66df3b9ecd2037bf252"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbda76961eb8f27e6ad3c84d1dc56d5bc61ba8f02bd20fcf3450bd421c2fcc9c"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feea693c452d85ea0015ebe3bb9cd15b6f49acc1a31c28b3c50f4db0f8fb1e71"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da98815f82dce0cb31fd1e873a0cb30934971d15b74e0d78cf21f9e1b05953f"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5adf383c73f2d49ad15ff363a8748319ff84c371eed59ffd0127355d6ea1da"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56856b871146bfead25fbcaed098269d90b744eea5cb32a952df00d542cdd368"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-win32.whl", hash = "sha256:943aa74a11f5806ab68278284a4ddd282d3fb348a0e96db9b42cb81bf731acdc"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-win_amd64.whl", hash = "sha256:c6c4da4843e0dabde41b8f2e8147438330924114f541949e6318358a56d1875a"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a3d4e7a472bfff2d28db838669fc437964e8af8df8ee1e4548e92710929adc"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3dd67b5d69794cfe82862c002512683b3db038b99002171f624712fa71aeaa"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61e2e41656a673b777e2f0cbbe545323dbe0d32312f590b1bc09da1de6c2a02"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0315d9125a38026227f559488fe7f7cee1bd2fbc19f9fd637739dc50bb6380b2"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af8ce2d31679006e7b747d30a89cd3ac1ec304c3d4c20973f0f4ad58e2d1c4c9"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81ba314a08c7ab701e621b7ad079c0c933c58cdef88593c59b90b996e8b58fa5"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-win32.whl", hash = "sha256:1ee8bd6d68578e517943f5ebff3afbd93fc65f7ef8f23becab9fa8fb315afb1d"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-win_amd64.whl", hash = "sha256:ad7acbe95bac70e4e687a4dc9ae3f7a2f467aa6597049eeb6d4a662ecd990bb6"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d3499008ddec83127ab286c6f6ec82a34f39c9817f020f75eca96155f9765097"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b66fcd38659cab5d29e8de5409cdf91e9986817703e1078b2fdaad731ea66f5"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea30da1e76cb1acc5b72e204a920a3a7678d9d52f688f087dc08e54e2754c67"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:124202b4e0edea7f08a4db8c81cc7859012f90a0d14ba2bf07c099aff6e96462"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e23b88c69497a6322b5796c0781400692eca1ae5532821b39ce81a48c395aae9"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b6303bfd78fb3221847723104d152e5972c22367ff66edf09120fcde5ddc2e2"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-win32.whl", hash = "sha256:a921002be69ac3ab2cf0c3017c4e6a3377f800f1fca7f254c13b5f1a2f10022c"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-win_amd64.whl", hash = "sha256:b4a2cf92995635b64876dc141af0ef089c6eea7e05898d8d8865e71a326c0385"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e91b5e341f8c7f1e5020db8e5602f3ed045a29f8e27f7f565e0bdee3338f2c7"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c7b78dfc7278329f27be02c44abc0d69fe235495bb8e16ec7ef1b1a17952db"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eba73ef2c30695cb7eabcdb33bb3d0b878595737479e152468f3ba97a9c22a4"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5df5d1dafb8eee89384fb7a1f79128118bc0ba50ce0db27a40750f6f91aa99d5"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2858bbab1681ee5406650202950dc8f00e83b06a198741b7c656e63818633526"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-win32.whl", hash = "sha256:9461802f2e965de5cff80c5a13bc945abea7edaa1d29360b485c3d2b56cdb075"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-win_amd64.whl", hash = "sha256:a6bec1c010a6d65b3ed88c863d56b9ea5eeefdf62b5e39cafd08c65f5ce5198b"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:843a882cadebecc655a68bd9a5b8aa39b3c52f4a9a5572a3036fb1bb2ccdc197"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dbb990612c36163c6072723523d2be7c3eb1517bbdd63fe50449f56afafd1133"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7e4baf9161d076b9a7e432fce06217b9bd90cfb8f1d543d6e8c4595627edb9"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a5354cb4de9b64bccb6ea33162cb83e03dbefa0d892db88a672f5aad638a75"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fffcc8edc508801ed2e6a4e7b0d150a62196fd28b4e16ab9f65192e8186102b6"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aca7b6d99a4541b2ebab4494f6c8c2f947e0df4ac859ced575238e1d6ca5716b"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-win32.whl", hash = "sha256:8c7f10720fc34d14abad5b647bc8202202f4948498927d9f1b4df0fb1cf391b7"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-win_amd64.whl", hash = "sha256:243feb6882b06a2af68ecf4bec8813d99452a1b62ba2be917ce6283852cf701b"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc4974d3684f28b61b9a90fcb4c41fb340fd4b6a50c04365704a4da5a9603b05"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87724e7ed2a936fdda2c05dbd99d395c91ea3c96f029a033a4a20e008dd876bf"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68722e6a550f5de2e3cfe9da6afb9a7dd15ef7032afa5651b0f0c6b3adb8815d"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328529f7c7f90adcd65aed06a161851f83f475c2f664a898af574893f55d9e53"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:df40c16a7e8be7413b885c9bf900d402918cc848be08a59b022478804ea076b8"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:426f2fa71331a64f5132369ede5171c52fd1df1bd9727ce621f38b5b24f48750"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-win32.whl", hash = "sha256:33157920b233bc542ce497a81a2e1452e685a11834c5763933b440fedd1d8e2d"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-win_amd64.whl", hash = "sha256:2f60843068e432311c886c5f03c4664acaef507cf716f6c60d5fde7265be9d7b"}, - {file = "SQLAlchemy-2.0.28-py3-none-any.whl", hash = "sha256:78bb7e8da0183a8301352d569900d9d3594c48ac21dc1c2ec6b3121ed8b6c986"}, - {file = "SQLAlchemy-2.0.28.tar.gz", hash = "sha256:dd53b6c4e6d960600fd6532b79ee28e2da489322fcf6648738134587faf767b6"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:95d0b2cf8791ab5fb9e3aa3d9a79a0d5d51f55b6357eecf532a120ba3b5524db"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:243f92596f4fd4c8bd30ab8e8dd5965afe226363d75cab2468f2c707f64cd83b"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ea54f7300553af0a2a7235e9b85f4204e1fc21848f917a3213b0e0818de9a24"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173f5f122d2e1bff8fbd9f7811b7942bead1f5e9f371cdf9e670b327e6703ebd"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:196958cde924a00488e3e83ff917be3b73cd4ed8352bbc0f2989333176d1c54d"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd90c221ed4e60ac9d476db967f436cfcecbd4ef744537c0f2d5291439848768"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-win32.whl", hash = "sha256:3166dfff2d16fe9be3241ee60ece6fcb01cf8e74dd7c5e0b64f8e19fab44911b"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-win_amd64.whl", hash = "sha256:6831a78bbd3c40f909b3e5233f87341f12d0b34a58f14115c9e94b4cdaf726d3"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7db3db284a0edaebe87f8f6642c2b2c27ed85c3e70064b84d1c9e4ec06d5d84"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:430093fce0efc7941d911d34f75a70084f12f6ca5c15d19595c18753edb7c33b"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79cb400c360c7c210097b147c16a9e4c14688a6402445ac848f296ade6283bbc"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1b30f31a36c7f3fee848391ff77eebdd3af5750bf95fbf9b8b5323edfdb4ec"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fddde2368e777ea2a4891a3fb4341e910a056be0bb15303bf1b92f073b80c02"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80bd73ea335203b125cf1d8e50fef06be709619eb6ab9e7b891ea34b5baa2287"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-win32.whl", hash = "sha256:6daeb8382d0df526372abd9cb795c992e18eed25ef2c43afe518c73f8cccb721"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-win_amd64.whl", hash = "sha256:5bc08e75ed11693ecb648b7a0a4ed80da6d10845e44be0c98c03f2f880b68ff4"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:53e68b091492c8ed2bd0141e00ad3089bcc6bf0e6ec4142ad6505b4afe64163e"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bcd18441a49499bf5528deaa9dee1f5c01ca491fc2791b13604e8f972877f812"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:165bbe0b376541092bf49542bd9827b048357f4623486096fc9aaa6d4e7c59a2"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3330415cd387d2b88600e8e26b510d0370db9b7eaf984354a43e19c40df2e2b"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97b850f73f8abbffb66ccbab6e55a195a0eb655e5dc74624d15cff4bfb35bd74"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee4c6917857fd6121ed84f56d1dc78eb1d0e87f845ab5a568aba73e78adf83"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-win32.whl", hash = "sha256:fbb034f565ecbe6c530dff948239377ba859420d146d5f62f0271407ffb8c580"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-win_amd64.whl", hash = "sha256:707c8f44931a4facd4149b52b75b80544a8d824162602b8cd2fe788207307f9a"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:24af3dc43568f3780b7e1e57c49b41d98b2d940c1fd2e62d65d3928b6f95f021"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60ed6ef0a35c6b76b7640fe452d0e47acc832ccbb8475de549a5cc5f90c2c06"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413c85cd0177c23e32dee6898c67a5f49296640041d98fddb2c40888fe4daa2e"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:25691f4adfb9d5e796fd48bf1432272f95f4bbe5f89c475a788f31232ea6afba"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:526ce723265643dbc4c7efb54f56648cc30e7abe20f387d763364b3ce7506c82"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-win32.whl", hash = "sha256:13be2cc683b76977a700948411a94c67ad8faf542fa7da2a4b167f2244781cf3"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-win_amd64.whl", hash = "sha256:e54ef33ea80d464c3dcfe881eb00ad5921b60f8115ea1a30d781653edc2fd6a2"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43f28005141165edd11fbbf1541c920bd29e167b8bbc1fb410d4fe2269c1667a"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b68094b165a9e930aedef90725a8fcfafe9ef95370cbb54abc0464062dbf808f"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1e03db964e9d32f112bae36f0cc1dcd1988d096cfd75d6a588a3c3def9ab2b"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:203d46bddeaa7982f9c3cc693e5bc93db476ab5de9d4b4640d5c99ff219bee8c"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ae92bebca3b1e6bd203494e5ef919a60fb6dfe4d9a47ed2453211d3bd451b9f5"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9661268415f450c95f72f0ac1217cc6f10256f860eed85c2ae32e75b60278ad8"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-win32.whl", hash = "sha256:895184dfef8708e15f7516bd930bda7e50ead069280d2ce09ba11781b630a434"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-win_amd64.whl", hash = "sha256:6e7cde3a2221aa89247944cafb1b26616380e30c63e37ed19ff0bba5e968688d"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dbcdf987f3aceef9763b6d7b1fd3e4ee210ddd26cac421d78b3c206d07b2700b"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ce119fc4ce0d64124d37f66a6f2a584fddc3c5001755f8a49f1ca0a177ef9796"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a17d8fac6df9835d8e2b4c5523666e7051d0897a93756518a1fe101c7f47f2f0"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ebc11c54c6ecdd07bb4efbfa1554538982f5432dfb8456958b6d46b9f834bb7"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e6965346fc1491a566e019a4a1d3dfc081ce7ac1a736536367ca305da6472a8"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:220574e78ad986aea8e81ac68821e47ea9202b7e44f251b7ed8c66d9ae3f4278"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-win32.whl", hash = "sha256:b75b00083e7fe6621ce13cfce9d4469c4774e55e8e9d38c305b37f13cf1e874c"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-win_amd64.whl", hash = "sha256:c29d03e0adf3cc1a8c3ec62d176824972ae29b67a66cbb18daff3062acc6faa8"}, + {file = "SQLAlchemy-2.0.34-py3-none-any.whl", hash = "sha256:7286c353ee6475613d8beff83167374006c6b3e3f0e6491bfe8ca610eb1dec0f"}, + {file = "sqlalchemy-2.0.34.tar.gz", hash = "sha256:10d8f36990dd929690666679b0f42235c159a7051534adb135728ee52828dd22"}, ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} typing-extensions = ">=4.6.0" [package.extras] @@ -1659,12 +1759,12 @@ tornado = "*" [[package]] name = "thrift" -version = "0.16.0" +version = "0.20.0" description = "Python bindings for the Apache Thrift RPC system" optional = false python-versions = "*" files = [ - {file = "thrift-0.16.0.tar.gz", hash = "sha256:2b5b6488fcded21f9d312aa23c9ff6a0195d0f6ae26ddbd5ad9e3e25dfc14408"}, + {file = "thrift-0.20.0.tar.gz", hash = "sha256:4dd662eadf6b8aebe8a41729527bd69adf6ceaa2a8681cbef64d1273b3e8feba"}, ] [package.dependencies] @@ -1677,13 +1777,13 @@ twisted = ["twisted"] [[package]] name = "tomlkit" -version = "0.12.4" +version = "0.13.2" description = "Style preserving TOML library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"}, - {file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"}, + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, ] [[package]] @@ -1708,13 +1808,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -1736,13 +1836,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "werkzeug" -version = "3.0.1" +version = "3.0.4" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, - {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, + {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, + {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, ] [package.dependencies] @@ -1781,4 +1881,4 @@ xmlsec = ["xmlsec (>=0.6.1)"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "bf95b2ec5b5f61f7f67c38f71ebdee994d018d51ebe73d16759363cc358c6f0c" +content-hash = "374eb1432cd78d908b2026a53d9f3ca94e0cc0e5bc0eb7a803c2d0d64368139c" diff --git a/bcol-api/pyproject.toml b/bcol-api/pyproject.toml index ec6d9a19e..6c102d737 100644 --- a/bcol-api/pyproject.toml +++ b/bcol-api/pyproject.toml @@ -9,7 +9,7 @@ readme = "README.md" python = "^3.12" sbc-common-components = {git = "https://github.com/bcgov/sbc-common-components.git", subdirectory = "python"} flask-jwt-oidc = {git = "https://github.com/thorwolpert/flask-jwt-oidc.git"} -gunicorn = "^21.2.0" +gunicorn = "^22.0.0" flask = "^3.0.2" flask-script = "^2.0.6" flask-moment = "^1.0.5" @@ -17,10 +17,10 @@ flask-restx = "^1.3.0" python-dotenv = "^1.0.1" psycopg2-binary = "^2.9.9" jsonschema = "4.17.3" -requests = "^2.31.0" +requests = "^2.32.2" zeep = "^4.2.1" python-ldap = "^3.4.4" -sentry-sdk = {extras = ["flask"], version = "^1.42.0"} +sentry-sdk = {extras = ["flask"], version = "^2.8.0"} attrs = "^23.2.0" werkzeug = "^3.0.1" jaeger-client = "^4.8.0" @@ -32,7 +32,7 @@ jinja2 = "^3.1.3" [tool.poetry.group.dev.dependencies] pytest = "^8.1.1" pytest-mock = "^3.12.0" -requests = "^2.31.0" +requests = "^2.32.2" pyhamcrest = "^2.1.0" pytest-cov = "^4.1.0" flake8 = "^7.0.0" diff --git a/bcol-api/setup.cfg b/bcol-api/setup.cfg index 7b0dffc81..50306ef92 100755 --- a/bcol-api/setup.cfg +++ b/bcol-api/setup.cfg @@ -45,7 +45,7 @@ per-file-ignores = max_line_length = 120 ignore = E501 docstring-min-length=10 -notes=FIXME,XXX # TODO is ignored +notes=FIXME,XXX match_dir = src/bcol_api ignored-modules=flask_sqlalchemy sqlalchemy diff --git a/bcol-api/src/bcol_api/resources/bcol_payment.py b/bcol-api/src/bcol_api/resources/bcol_payment.py index ddd2d3669..4323e90f8 100755 --- a/bcol-api/src/bcol_api/resources/bcol_payment.py +++ b/bcol-api/src/bcol_api/resources/bcol_payment.py @@ -41,6 +41,7 @@ class AccountPayment(Resource): def post(): """Create a payment record in BCOL.""" try: + is_apply_charge = False if _jwt.validate_roles([Role.STAFF.value, Role.EDIT.value]) or _jwt.validate_roles([Role.SYSTEM.value]): is_apply_charge = True elif _jwt.validate_roles([Role.ACCOUNT_HOLDER.value]): diff --git a/bcol-api/src/bcol_api/resources/ops.py b/bcol-api/src/bcol_api/resources/ops.py index c9a86f32c..c97f64e9a 100755 --- a/bcol-api/src/bcol_api/resources/ops.py +++ b/bcol-api/src/bcol_api/resources/ops.py @@ -39,5 +39,4 @@ class Readyz(Resource): @staticmethod def get(): """Return a JSON object that identifies if the service is setupAnd ready to work.""" - # TODO: add a poll to the DB when called return {'message': 'api is ready'}, 200 diff --git a/bcol-api/src/bcol_api/version.py b/bcol-api/src/bcol_api/version.py index 9576ba6f8..6f57debc9 100755 --- a/bcol-api/src/bcol_api/version.py +++ b/bcol-api/src/bcol_api/version.py @@ -22,4 +22,4 @@ Development release segment: .devN """ -__version__ = '0.1.0a0.dev' # pylint: disable=invalid-name +__version__ = '1.0.0' # pylint: disable=invalid-name diff --git a/docs/docs/api_contract/pay-api.yaml b/docs/docs/api_contract/pay-api.yaml index 0e68daed8..667d5c46d 100644 --- a/docs/docs/api_contract/pay-api.yaml +++ b/docs/docs/api_contract/pay-api.yaml @@ -5,7 +5,7 @@ info:

BC Registries Pay system is used by the BC Registries account services and integrated systems to create payment transactions to pay for the products purchased. This abstracts the integrated systems from the underlying payment integrations and payment methods.
With this API you can submit the following transactions:

All requests must include an Account ID.

- version: 1.0.6 + version: 1.0.7 contact: name: BC Registries paths: @@ -1617,6 +1617,9 @@ components: disbursementDate: type: string description: date when disbursement status code was set + disbursementReversalDate: + type: string + description: date when disbursement reversal status code was set overdueDate: type: string description: date when payment is overdue (EFT / ONLINE_BANKING) diff --git a/docs/docs/architecture/Pay Flow.png b/docs/docs/architecture/Pay Flow.png index 4da3f91303ace623367ab738407a5375e7443c9f..4b1dcb03d8adeac10e74660fd92751c722231de7 100644 GIT binary patch literal 46523 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 DdW=5% literal 131750 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 diff --git a/jobs/ftp-poller/Dockerfile b/jobs/ftp-poller/Dockerfile index 616ea93d0..578b8af76 100644 --- a/jobs/ftp-poller/Dockerfile +++ b/jobs/ftp-poller/Dockerfile @@ -85,6 +85,7 @@ COPY --chown=web:web ./poetry.lock ./pyproject.toml . RUN --mount=type=cache,target="$POETRY_CACHE_DIR" \ echo "$APP_ENV" \ && poetry version \ + && poetry config installer.max-workers 1 \ && poetry run pip install -U pip \ && poetry install \ $(if [ -z ${APP_ENV+x} ] | [ "$APP_ENV" = 'production' ]; then echo '--only main'; fi) \ diff --git a/jobs/ftp-poller/Makefile b/jobs/ftp-poller/Makefile index cc3dce5d2..e6c179c09 100644 --- a/jobs/ftp-poller/Makefile +++ b/jobs/ftp-poller/Makefile @@ -39,6 +39,7 @@ clean-test: ## clean test files install: clean unset HOME ## unset HOME because it's in the DEV .env file, will cause permissions issues pip install poetry ;\ + poetry config installer.max-workers 1 poetry install ################################################################################# diff --git a/jobs/ftp-poller/config.py b/jobs/ftp-poller/config.py index dd5c03c04..f2c162b0e 100644 --- a/jobs/ftp-poller/config.py +++ b/jobs/ftp-poller/config.py @@ -74,14 +74,17 @@ class _Config(object): # pylint: disable=too-few-public-methods CGI_SFTP_DIRECTORY = os.getenv('CGI_SFTP_DIRECTORY', '/data') # EFT FTP CONFIG + BCREG_EFT_FTP_PRIVATE_KEY_LOCATION = os.getenv('BCREG_EFT_FTP_PRIVATE_KEY_LOCATION', + '/ftp-poller/key/eft_sftp_priv_key') EFT_SFTP_HOST = os.getenv('EFT_SFTP_HOST', 'localhost') EFT_SFTP_USER_NAME = os.getenv('EFT_SFTP_USER_NAME', 'foo') EFT_SFTP_PASSWORD = os.getenv('EFT_SFTP_PASSWORD', '') - EFT_SFTP_DIRECTORY = os.getenv('EFT_SFTP_DIRECTORY', '/eft') - EFT_SFTP_BACKUP_DIRECTORY = os.getenv('EFT_SFTP_BACKUP_DIRECTORY', '/backup') + EFT_SFTP_DIRECTORY = os.getenv('EFT_SFTP_DIRECTORY', '/outgoing') + EFT_SFTP_BACKUP_DIRECTORY = os.getenv('EFT_SFTP_BACKUP_DIRECTORY', '/outgoing-backup') EFT_SFTP_VERIFY_HOST = os.getenv('EFT_SFTP_VERIFY_HOST', 'True') EFT_SFTP_PORT = os.getenv('EFT_SFTP_PORT', 22) EFT_SFTP_HOST_KEY = os.getenv('EFT_SFTP_HOST_KEY', '') + BCREG_EFT_FTP_PRIVATE_KEY_PASSPHRASE = os.getenv('BCREG_EFT_FTP_PRIVATE_KEY_PASSPHRASE', '') # CGI File specific configs CGI_TRIGGER_FILE_SUFFIX = os.getenv('CGI_TRIGGER_FILE_SUFFIX', '.TRG') @@ -111,7 +114,6 @@ class _Config(object): # pylint: disable=too-few-public-methods 'FTP_PRIVATE_KEY_LOCATION': BCREG_CGI_FTP_PRIVATE_KEY_LOCATION, # different user.so not same as CAS 'BCREG_FTP_PRIVATE_KEY_PASSPHRASE': BCREG_CGI_FTP_PRIVATE_KEY_PASSPHRASE }, - # FUTURE - specific configuration values TBD - initial set up code 'EFT': { 'SFTP_HOST': EFT_SFTP_HOST, 'SFTP_USERNAME': EFT_SFTP_USER_NAME, @@ -119,8 +121,8 @@ class _Config(object): # pylint: disable=too-few-public-methods 'SFTP_VERIFY_HOST': EFT_SFTP_VERIFY_HOST, 'SFTP_HOST_KEY': EFT_SFTP_HOST_KEY, 'SFTP_PORT': EFT_SFTP_PORT, - 'FTP_PRIVATE_KEY_LOCATION': BCREG_FTP_PRIVATE_KEY_LOCATION, - 'BCREG_FTP_PRIVATE_KEY_PASSPHRASE': BCREG_FTP_PRIVATE_KEY_PASSPHRASE + 'FTP_PRIVATE_KEY_LOCATION': BCREG_EFT_FTP_PRIVATE_KEY_LOCATION, + 'BCREG_FTP_PRIVATE_KEY_PASSPHRASE': BCREG_EFT_FTP_PRIVATE_KEY_PASSPHRASE } } diff --git a/jobs/ftp-poller/poetry.lock b/jobs/ftp-poller/poetry.lock index e246b839e..c945e7b47 100644 --- a/jobs/ftp-poller/poetry.lock +++ b/jobs/ftp-poller/poetry.lock @@ -1,4 +1,140 @@ -# 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 = "aiohappyeyeballs" +version = "2.4.0" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, + {file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"}, +] + +[[package]] +name = "aiohttp" +version = "3.10.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683"}, + {file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef"}, + {file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058"}, + {file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072"}, + {file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6"}, + {file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12"}, + {file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987"}, + {file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04"}, + {file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511"}, + {file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a"}, + {file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11"}, + {file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1"}, + {file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862"}, + {file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" [[package]] name = "alembic" @@ -243,13 +379,13 @@ ujson = ["ujson (>=5.7.0)"] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.7.4" 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"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -521,43 +657,38 @@ pytz = ">2021.1" [[package]] name = "cryptography" -version = "42.0.5" +version = "43.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, + {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, + {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, + {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, + {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, + {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, + {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, + {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, ] [package.dependencies] @@ -570,7 +701,7 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] @@ -758,13 +889,13 @@ Flask = "*" [[package]] name = "flask-cors" -version = "4.0.0" +version = "5.0.0" description = "A Flask extension adding a decorator for CORS support" optional = false python-versions = "*" files = [ - {file = "Flask-Cors-4.0.0.tar.gz", hash = "sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"}, - {file = "Flask_Cors-4.0.0-py2.py3-none-any.whl", hash = "sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783"}, + {file = "Flask_Cors-5.0.0-py2.py3-none-any.whl", hash = "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc"}, + {file = "flask_cors-5.0.0.tar.gz", hash = "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef"}, ] [package.dependencies] @@ -911,6 +1042,92 @@ files = [ flask = ">=2.2.5" sqlalchemy = ">=2.0.16" +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + [[package]] name = "gcp-queue" version = "0.3.0" @@ -928,9 +1145,9 @@ simple-cloudevent = {git = "https://github.com/daxiom/simple-cloudevent.py.git"} [package.source] type = "git" -url = "https://github.com/seeker25/sbc-connect-common.git" -reference = "add_ordering" -resolved_reference = "ec04b8f68a20885fbd1aeb758059709dc271fd8f" +url = "https://github.com/bcgov/sbc-connect-common.git" +reference = "main" +resolved_reference = "c898988d239dc261b2b186465a1887f15512c102" subdirectory = "python/gcp-queue" [[package]] @@ -1185,22 +1402,23 @@ protobuf = ">=4.21.6" [[package]] name = "gunicorn" -version = "21.2.0" +version = "22.0.0" description = "WSGI HTTP Server for UNIX" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, - {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, + {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"}, + {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"}, ] [package.dependencies] packaging = "*" [package.extras] -eventlet = ["eventlet (>=0.24.1)"] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] gevent = ["gevent (>=1.4.0)"] setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] tornado = ["tornado (>=0.2)"] [[package]] @@ -1219,13 +1437,13 @@ python-dateutil = "*" [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -1285,13 +1503,13 @@ tests = ["codecov", "coverage", "flake8", "flake8-quotes", "flake8-typing-import [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -1526,6 +1744,105 @@ pycryptodome = "*" typing-extensions = "*" urllib3 = "*" +[[package]] +name = "multidict" +version = "6.0.5" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, +] + [[package]] name = "opentracing" version = "2.4.0" @@ -1581,38 +1898,39 @@ files = [] develop = false [package.dependencies] +aiohttp = "^3.9.5" alembic = "1.13.1" attrs = "23.2.0" blinker = "1.7.0" cachelib = "0.9.0" cachetools = "5.3.3" cattrs = "23.2.3" -certifi = "2024.2.2" +certifi = "2024.7.4" cffi = "1.16.0" charset-normalizer = "3.3.2" click = "8.1.7" croniter = "2.0.2" -cryptography = "42.0.5" +cryptography = "43.0.1" dpath = "2.1.6" ecdsa = "0.18.0" expiringdict = "1.2.2" flask = "3.0.2" flask-caching = "2.3.0" -flask-cors = "4.0.0" +flask-cors = "5.0.0" flask-jwt-oidc = {git = "https://github.com/seeker25/flask-jwt-oidc.git"} flask-marshmallow = "1.2.0" flask-migrate = "4.0.7" flask-moment = "1.0.5" flask-script = "2.0.6" flask-sqlalchemy = "3.1.1" -gcp-queue = {git = "https://github.com/seeker25/sbc-connect-common.git", branch = "add_ordering", subdirectory = "python/gcp-queue"} +gcp-queue = {git = "https://github.com/bcgov/sbc-connect-common.git", branch = "main", subdirectory = "python/gcp-queue"} greenlet = "3.0.3" -gunicorn = "21.2.0" +gunicorn = "22.0.0" holidays = "0.37" -idna = "3.6" +idna = "3.7" itsdangerous = "2.1.2" jaeger-client = "4.8.0" -jinja2 = "3.1.3" +jinja2 = "3.1.4" jsonschema = "4.17.3" launchdarkly-eventsource = "1.1.1" launchdarkly-server-sdk = "8.2.1" @@ -1640,23 +1958,24 @@ requests = "2.32.2" rsa = "4.9" sbc-common-components = {git = "https://github.com/bcgov/sbc-common-components.git", subdirectory = "python"} semver = "3.0.2" -sentry-sdk = "1.41.0" +sentry-sdk = {version = "^2.8.0", extras = ["flask"]} +setuptools = "^73.0.1" six = "1.16.0" -sql-versioning = {git = "https://github.com/bcgov/lear.git", branch = "feature-legal-name", subdirectory = "python/common/sql-versioning"} +sql-versioning = {git = "https://github.com/bcgov/sbc-connect-common.git", branch = "main", subdirectory = "python/sql-versioning"} sqlalchemy = "2.0.28" sqlalchemy-utils = "0.41.1" threadloop = "1.0.2" thrift = "0.16.0" -tornado = "6.4" +tornado = "6.4.1" typing-extensions = "4.10.0" -urllib3 = "2.2.1" -werkzeug = "3.0.1" +urllib3 = "2.2.2" +werkzeug = "3.0.3" [package.source] type = "git" -url = "https://github.com/seeker25/sbc-pay.git" -reference = "21721" -resolved_reference = "a3ae9f977e42be640125c3605b934562f7974887" +url = "https://github.com/bcgov/sbc-pay.git" +reference = "main" +resolved_reference = "d6b54079b123c7f9ca7bb66e32f044f5fed576e7" subdirectory = "pay-api" [[package]] @@ -2344,38 +2663,47 @@ files = [ [[package]] name = "sentry-sdk" -version = "1.41.0" +version = "2.13.0" description = "Python client for Sentry (https://sentry.io)" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "sentry-sdk-1.41.0.tar.gz", hash = "sha256:4f2d6c43c07925d8cd10dfbd0970ea7cb784f70e79523cca9dbcd72df38e5a46"}, - {file = "sentry_sdk-1.41.0-py2.py3-none-any.whl", hash = "sha256:be4f8f4b29a80b6a3b71f0f31487beb9e296391da20af8504498a328befed53f"}, + {file = "sentry_sdk-2.13.0-py2.py3-none-any.whl", hash = "sha256:6beede8fc2ab4043da7f69d95534e320944690680dd9a963178a49de71d726c6"}, + {file = "sentry_sdk-2.13.0.tar.gz", hash = "sha256:8d4a576f7a98eb2fdb40e13106e41f330e5c79d72a68be1316e7852cf4995260"}, ] [package.dependencies] +blinker = {version = ">=1.1", optional = true, markers = "extra == \"flask\""} certifi = "*" -urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} +flask = {version = ">=0.11", optional = true, markers = "extra == \"flask\""} +markupsafe = {version = "*", optional = true, markers = "extra == \"flask\""} +urllib3 = ">=1.26.11" [package.extras] aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] arq = ["arq (>=0.23)"] asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] chalice = ["chalice (>=1.16.0)"] clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] -grpcio = ["grpcio (>=1.21.1)"] +grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] +huggingface-hub = ["huggingface-hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] +litestar = ["litestar (>=2.0.0)"] loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] -opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +opentelemetry-experimental = ["opentelemetry-distro"] pure-eval = ["asttokens", "executing", "pure-eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] @@ -2385,22 +2713,23 @@ sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] -tornado = ["tornado (>=5)"] +tornado = ["tornado (>=6)"] [[package]] name = "setuptools" -version = "70.0.0" +version = "73.0.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, - {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, + {file = "setuptools-73.0.1-py3-none-any.whl", hash = "sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e"}, + {file = "setuptools-73.0.1.tar.gz", hash = "sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "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] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.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-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] [[package]] name = "simple-cloudevent" @@ -2453,10 +2782,10 @@ develop = false [package.source] type = "git" -url = "https://github.com/bcgov/lear.git" -reference = "feature-legal-name" -resolved_reference = "e5a432d1460dc84208465ef35c0c81ab02e66f51" -subdirectory = "python/common/sql-versioning" +url = "https://github.com/bcgov/sbc-connect-common.git" +reference = "main" +resolved_reference = "c898988d239dc261b2b186465a1887f15512c102" +subdirectory = "python/sql-versioning" [[package]] name = "sqlalchemy" @@ -2628,22 +2957,22 @@ files = [ [[package]] name = "tornado" -version = "6.4" +version = "6.4.1" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false -python-versions = ">= 3.8" +python-versions = ">=3.8" files = [ - {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, - {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"}, - {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"}, - {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, - {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, ] [[package]] @@ -2659,13 +2988,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" 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"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -2676,13 +3005,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "werkzeug" -version = "3.0.1" +version = "3.0.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, - {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, + {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, + {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, ] [package.dependencies] @@ -2705,7 +3034,112 @@ files = [ [package.extras] test = ["pytest (>=6.0.0)", "setuptools (>=65)"] +[[package]] +name = "yarl" +version = "1.9.11" +description = "Yet another URL library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "yarl-1.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:79e08c691deae6fcac2fdde2e0515ac561dd3630d7c8adf7b1e786e22f1e193b"}, + {file = "yarl-1.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:752f4b5cf93268dc73c2ae994cc6d684b0dad5118bc87fbd965fd5d6dca20f45"}, + {file = "yarl-1.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:441049d3a449fb8756b0535be72c6a1a532938a33e1cf03523076700a5f87a01"}, + {file = "yarl-1.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3dfe17b4aed832c627319da22a33f27f282bd32633d6b145c726d519c89fbaf"}, + {file = "yarl-1.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:67abcb7df27952864440c9c85f1c549a4ad94afe44e2655f77d74b0d25895454"}, + {file = "yarl-1.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6de3fa29e76fd1518a80e6af4902c44f3b1b4d7fed28eb06913bba4727443de3"}, + {file = "yarl-1.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fee45b3bd4d8d5786472e056aa1359cc4dc9da68aded95a10cd7929a0ec661fe"}, + {file = "yarl-1.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c59b23886234abeba62087fd97d10fb6b905d9e36e2f3465d1886ce5c0ca30df"}, + {file = "yarl-1.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d93c612b2024ac25a3dc01341fd98fdd19c8c5e2011f3dcd084b3743cba8d756"}, + {file = "yarl-1.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4d368e3b9ecd50fa22017a20c49e356471af6ae91c4d788c6e9297e25ddf5a62"}, + {file = "yarl-1.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5b593acd45cdd4cf6664d342ceacedf25cd95263b83b964fddd6c78930ea5211"}, + {file = "yarl-1.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:224f8186c220ff00079e64bf193909829144d4e5174bb58665ef0da8bf6955c4"}, + {file = "yarl-1.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:91c478741d7563a12162f7a2db96c0d23d93b0521563f1f1f0ece46ea1702d33"}, + {file = "yarl-1.9.11-cp310-cp310-win32.whl", hash = "sha256:1cdb8f5bb0534986776a43df84031da7ff04ac0cf87cb22ae8a6368231949c40"}, + {file = "yarl-1.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:498439af143b43a2b2314451ffd0295410aa0dcbdac5ee18fc8633da4670b605"}, + {file = "yarl-1.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e290de5db4fd4859b4ed57cddfe793fcb218504e65781854a8ac283ab8d5518"}, + {file = "yarl-1.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e5f50a2e26cc2b89186f04c97e0ec0ba107ae41f1262ad16832d46849864f914"}, + {file = "yarl-1.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4a0e724a28d7447e4d549c8f40779f90e20147e94bf949d490402eee09845c6"}, + {file = "yarl-1.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85333d38a4fa5997fa2ff6fd169be66626d814b34fa35ec669e8c914ca50a097"}, + {file = "yarl-1.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ff184002ee72e4b247240e35d5dce4c2d9a0e81fdbef715dde79ab4718aa541"}, + {file = "yarl-1.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:675004040f847c0284827f44a1fa92d8baf425632cc93e7e0aa38408774b07c1"}, + {file = "yarl-1.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30703a7ade2b53f02e09a30685b70cd54f65ed314a8d9af08670c9a5391af1b"}, + {file = "yarl-1.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7230007ab67d43cf19200ec15bc6b654e6b85c402f545a6fc565d254d34ff754"}, + {file = "yarl-1.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8c2cf0c7ad745e1c6530fe6521dfb19ca43338239dfcc7da165d0ef2332c0882"}, + {file = "yarl-1.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4567cc08f479ad80fb07ed0c9e1bcb363a4f6e3483a490a39d57d1419bf1c4c7"}, + {file = "yarl-1.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:95adc179a02949c4560ef40f8f650a008380766eb253d74232eb9c024747c111"}, + {file = "yarl-1.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:755ae9cff06c429632d750aa8206f08df2e3d422ca67be79567aadbe74ae64cc"}, + {file = "yarl-1.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:94f71d54c5faf715e92c8434b4a0b968c4d1043469954d228fc031d51086f143"}, + {file = "yarl-1.9.11-cp311-cp311-win32.whl", hash = "sha256:4ae079573efeaa54e5978ce86b77f4175cd32f42afcaf9bfb8a0677e91f84e4e"}, + {file = "yarl-1.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:9fae7ec5c9a4fe22abb995804e6ce87067dfaf7e940272b79328ce37c8f22097"}, + {file = "yarl-1.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:614fa50fd0db41b79f426939a413d216cdc7bab8d8c8a25844798d286a999c5a"}, + {file = "yarl-1.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ff64f575d71eacb5a4d6f0696bfe991993d979423ea2241f23ab19ff63f0f9d1"}, + {file = "yarl-1.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c23f6dc3d7126b4c64b80aa186ac2bb65ab104a8372c4454e462fb074197bc6"}, + {file = "yarl-1.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8f847cc092c2b85d22e527f91ea83a6cf51533e727e2461557a47a859f96734"}, + {file = "yarl-1.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63a5dc2866791236779d99d7a422611d22bb3a3d50935bafa4e017ea13e51469"}, + {file = "yarl-1.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c335342d482e66254ae94b1231b1532790afb754f89e2e0c646f7f19d09740aa"}, + {file = "yarl-1.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4a8c3dedd081cca134a21179aebe58b6e426e8d1e0202da9d1cafa56e01af3c"}, + {file = "yarl-1.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:504d19320c92532cabc3495fb7ed6bb599f3c2bfb45fed432049bf4693dbd6d0"}, + {file = "yarl-1.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b2a8e5eb18181060197e3d5db7e78f818432725c0759bc1e5a9d603d9246389"}, + {file = "yarl-1.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f568d70b7187f4002b6b500c0996c37674a25ce44b20716faebe5fdb8bd356e7"}, + {file = "yarl-1.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:735b285ea46ca7e86ad261a462a071d0968aade44e1a3ea2b7d4f3d63b5aab12"}, + {file = "yarl-1.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2d1c81c3b92bef0c1c180048e43a5a85754a61b4f69d6f84df8e4bd615bef25d"}, + {file = "yarl-1.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8d6e1c1562b53bd26efd38e886fc13863b8d904d559426777990171020c478a9"}, + {file = "yarl-1.9.11-cp312-cp312-win32.whl", hash = "sha256:aeba4aaa59cb709edb824fa88a27cbbff4e0095aaf77212b652989276c493c00"}, + {file = "yarl-1.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:569309a3efb8369ff5d32edb2a0520ebaf810c3059f11d34477418c90aa878fd"}, + {file = "yarl-1.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4915818ac850c3b0413e953af34398775b7a337babe1e4d15f68c8f5c4872553"}, + {file = "yarl-1.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ef9610b2f5a73707d4d8bac040f0115ca848e510e3b1f45ca53e97f609b54130"}, + {file = "yarl-1.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47c0a3dc8076a8dd159de10628dea04215bc7ddaa46c5775bf96066a0a18f82b"}, + {file = "yarl-1.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:545f2fbfa0c723b446e9298b5beba0999ff82ce2c126110759e8dac29b5deaf4"}, + {file = "yarl-1.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9137975a4ccc163ad5d7a75aad966e6e4e95dedee08d7995eab896a639a0bce2"}, + {file = "yarl-1.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b0c70c451d2a86f8408abced5b7498423e2487543acf6fcf618b03f6e669b0a"}, + {file = "yarl-1.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce2bd986b1e44528677c237b74d59f215c8bfcdf2d69442aa10f62fd6ab2951c"}, + {file = "yarl-1.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d7b717f77846a9631046899c6cc730ea469c0e2fb252ccff1cc119950dbc296"}, + {file = "yarl-1.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3a26a24bbd19241283d601173cea1e5b93dec361a223394e18a1e8e5b0ef20bd"}, + {file = "yarl-1.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c189bf01af155ac9882e128d9f3b3ad68a1f2c2f51404afad7201305df4e12b1"}, + {file = "yarl-1.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0cbcc2c54084b2bda4109415631db017cf2960f74f9e8fd1698e1400e4f8aae2"}, + {file = "yarl-1.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:30f201bc65941a4aa59c1236783efe89049ec5549dafc8cd2b63cc179d3767b0"}, + {file = "yarl-1.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:922ba3b74f0958a0b5b9c14ff1ef12714a381760c08018f2b9827632783a590c"}, + {file = "yarl-1.9.11-cp313-cp313-win32.whl", hash = "sha256:17107b4b8c43e66befdcbe543fff2f9c93f7a3a9f8e3a9c9ac42bffeba0e8828"}, + {file = "yarl-1.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:0324506afab4f2e176a93cb08b8abcb8b009e1f324e6cbced999a8f5dd9ddb76"}, + {file = "yarl-1.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4e4f820fde9437bb47297194f43d29086433e6467fa28fe9876366ad357bd7bb"}, + {file = "yarl-1.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dfa9b9d5c9c0dbe69670f5695264452f5e40947590ec3a38cfddc9640ae8ff89"}, + {file = "yarl-1.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e700eb26635ce665c018c8cfea058baff9b843ed0cc77aa61849d807bb82a64c"}, + {file = "yarl-1.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c305c1bdf10869b5e51facf50bd5b15892884aeae81962ae4ba061fc11217103"}, + {file = "yarl-1.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5b7b307140231ea4f7aad5b69355aba2a67f2d7bc34271cffa3c9c324d35b27"}, + {file = "yarl-1.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a744bdeda6c86cf3025c94eb0e01ccabe949cf385cd75b6576a3ac9669404b68"}, + {file = "yarl-1.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e8ed183c7a8f75e40068333fc185566472a8f6c77a750cf7541e11810576ea5"}, + {file = "yarl-1.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1db9a4384694b5d20bdd9cb53f033b0831ac816416ab176c8d0997835015d22"}, + {file = "yarl-1.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:70194da6e99713250aa3f335a7fa246b36adf53672a2bcd0ddaa375d04e53dc0"}, + {file = "yarl-1.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ddad5cfcda729e22422bb1c85520bdf2770ce6d975600573ac9017fe882f4b7e"}, + {file = "yarl-1.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ca35996e0a4bed28fa0640d9512d37952f6b50dea583bcc167d4f0b1e112ac7f"}, + {file = "yarl-1.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:61ec0e80970b21a8f3c4b97fa6c6d181c6c6a135dbc7b4a601a78add3feeb209"}, + {file = "yarl-1.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9636e4519f6c7558fdccf8f91e6e3b98df2340dc505c4cc3286986d33f2096c2"}, + {file = "yarl-1.9.11-cp38-cp38-win32.whl", hash = "sha256:58081cea14b8feda57c7ce447520e9d0a96c4d010cce54373d789c13242d7083"}, + {file = "yarl-1.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:7d2dee7d6485807c0f64dd5eab9262b7c0b34f760e502243dd83ec09d647d5e1"}, + {file = "yarl-1.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d65ad67f981e93ea11f87815f67d086c4f33da4800cf2106d650dd8a0b79dda4"}, + {file = "yarl-1.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:752c0d33b4aacdb147871d0754b88f53922c6dc2aff033096516b3d5f0c02a0f"}, + {file = "yarl-1.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:54cc24be98d7f4ff355ca2e725a577e19909788c0db6beead67a0dda70bd3f82"}, + {file = "yarl-1.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c82126817492bb2ebc946e74af1ffa10aacaca81bee360858477f96124be39a"}, + {file = "yarl-1.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8503989860d7ac10c85cb5b607fec003a45049cf7a5b4b72451e87893c6bb990"}, + {file = "yarl-1.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:475e09a67f8b09720192a170ad9021b7abf7827ffd4f3a83826317a705be06b7"}, + {file = "yarl-1.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afcac5bda602b74ff701e1f683feccd8cce0d5a21dbc68db81bf9bd8fd93ba56"}, + {file = "yarl-1.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaeffcb84faceb2923a94a8a9aaa972745d3c728ab54dd011530cc30a3d5d0c1"}, + {file = "yarl-1.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:51a6f770ac86477cd5c553f88a77a06fe1f6f3b643b053fcc7902ab55d6cbe14"}, + {file = "yarl-1.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3fcd056cb7dff3aea5b1ee1b425b0fbaa2fbf6a1c6003e88caf524f01de5f395"}, + {file = "yarl-1.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21e56c30e39a1833e4e3fd0112dde98c2abcbc4c39b077e6105c76bb63d2aa04"}, + {file = "yarl-1.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0a205ec6349879f5e75dddfb63e069a24f726df5330b92ce76c4752a436aac01"}, + {file = "yarl-1.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a5706821e1cf3c70dfea223e4e0958ea354f4e2af9420a1bd45c6b547297fb97"}, + {file = "yarl-1.9.11-cp39-cp39-win32.whl", hash = "sha256:cc295969f8c2172b5d013c0871dccfec7a0e1186cf961e7ea575d47b4d5cbd32"}, + {file = "yarl-1.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:55a67dd29367ce7c08a0541bb602ec0a2c10d46c86b94830a1a665f7fd093dfa"}, + {file = "yarl-1.9.11-py3-none-any.whl", hash = "sha256:c6f6c87665a9e18a635f0545ea541d9640617832af2317d4f5ad389686b4ed3d"}, + {file = "yarl-1.9.11.tar.gz", hash = "sha256:c7548a90cb72b67652e2cd6ae80e2683ee08fde663104528ac7df12d8ef271d2"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "1f4c9accb6acd19b22b5e3f26d9ed36eaf747db6b70a198a5d67d9e6f34ae5a4" +content-hash = "defec2f44fc170daa5b97f71d5b2ff379adc3431d9e9c547e020c5145306baf1" diff --git a/jobs/ftp-poller/pyproject.toml b/jobs/ftp-poller/pyproject.toml index 79758778e..2a32dad39 100644 --- a/jobs/ftp-poller/pyproject.toml +++ b/jobs/ftp-poller/pyproject.toml @@ -7,7 +7,6 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.12" -gunicorn = "^21.2.0" flask = "^3.0.2" flask-restplus = "^0.13.0" python-dotenv = "^1.0.1" @@ -23,7 +22,7 @@ jinja2 = "^3.1.3" protobuf = "4.25.3" launchdarkly-server-sdk = "^8.2.1" sbc-common-components = {git = "https://github.com/bcgov/sbc-common-components.git", subdirectory = "python"} -pay-api = {git = "https://github.com/seeker25/sbc-pay.git", subdirectory = "pay-api", branch = "21721"} +pay-api = {git = "https://github.com/bcgov/sbc-pay.git", subdirectory = "pay-api", branch = "main"} wheel = "^0.43.0" diff --git a/jobs/ftp-poller/setup.cfg b/jobs/ftp-poller/setup.cfg index c06c0dd9b..1e41becb7 100755 --- a/jobs/ftp-poller/setup.cfg +++ b/jobs/ftp-poller/setup.cfg @@ -45,7 +45,7 @@ per-file-ignores = max-line-length = 120 ignore = E501 docstring-min-length=10 -notes=FIXME,XXX # TODO is ignored +notes=FIXME,XXX match_dir = tasks ignored-modules=flask_sqlalchemy sqlalchemy diff --git a/jobs/notebook-report/requirements.txt b/jobs/notebook-report/requirements.txt index 3ad1d55da..5d14bea00 100644 --- a/jobs/notebook-report/requirements.txt +++ b/jobs/notebook-report/requirements.txt @@ -28,7 +28,7 @@ python-dotenv==0.13.0 requests==2.31.0 marshmallow==2.20.5 Werkzeug==0.16.1 -certifi==2023.7.22 +certifi==2024.7.4 urllib3==1.26.19 idna==3.7 pylint diff --git a/jobs/notebook-report/setup.cfg b/jobs/notebook-report/setup.cfg index 7a65bd293..feadfbf62 100644 --- a/jobs/notebook-report/setup.cfg +++ b/jobs/notebook-report/setup.cfg @@ -45,7 +45,7 @@ per-file-ignores = max-line-length = 120 ignore = E501 docstring-min-length=10 -notes=FIXME,XXX # TODO is ignored +notes=FIXME,XXX match_dir = tasks ignored-modules=flask_sqlalchemy sqlalchemy diff --git a/jobs/payment-jobs/Dockerfile b/jobs/payment-jobs/Dockerfile index 981cae2db..a86bf3edf 100644 --- a/jobs/payment-jobs/Dockerfile +++ b/jobs/payment-jobs/Dockerfile @@ -98,6 +98,7 @@ COPY --chown=web:web ./poetry.lock ./pyproject.toml . RUN --mount=type=cache,target="$POETRY_CACHE_DIR" \ echo "$APP_ENV" \ && poetry version \ + && poetry config installer.max-workers 1 \ && poetry run pip install -U pip \ && poetry install \ $(if [ -z ${APP_ENV+x} ] | [ "$APP_ENV" = 'production' ]; then echo '--only main'; fi) \ diff --git a/jobs/payment-jobs/Makefile b/jobs/payment-jobs/Makefile index dbad5fcf4..2c10cf1c8 100644 --- a/jobs/payment-jobs/Makefile +++ b/jobs/payment-jobs/Makefile @@ -37,8 +37,8 @@ clean-test: ## clean test files rm -fr htmlcov/ install: clean - unset HOME ## unset HOME because it's in the DEV .env file, will cause permissions issues pip install poetry ;\ + poetry config installer.max-workers 1 poetry install ################################################################################# diff --git a/jobs/payment-jobs/config.py b/jobs/payment-jobs/config.py index 5d45516c5..2d7358714 100644 --- a/jobs/payment-jobs/config.py +++ b/jobs/payment-jobs/config.py @@ -116,10 +116,10 @@ class _Config(object): # pylint: disable=too-few-public-methods AUTH_WEB_STATEMENT_URL = os.getenv('AUTH_WEB_STATEMENT_URL', 'account/orgId/settings/statements') REGISTRIES_LOGO_IMAGE_NAME = os.getenv('REGISTRIES_LOGO_IMAGE_NAME', 'bc_logo_for_email.png') - # PUB/SUB- PUB: account-mailer-dev - ACCOUNT_MAILER_TOPIC = os.getenv('ACCOUNT_MAILER_TOPIC', 'account-mailer-dev') + # GCP PubSub GCP_AUTH_KEY = os.getenv('AUTHPAY_GCP_AUTH_KEY', None) - + ACCOUNT_MAILER_TOPIC = os.getenv('ACCOUNT_MAILER_TOPIC', None) + AUTH_EVENT_TOPIC = os.getenv('AUTH_EVENT_TOPIC', None) CFS_ACCOUNT_DESCRIPTION = os.getenv('CFS_ACCOUNT_DESCRIPTION', 'BCR') CFS_INVOICE_PREFIX = os.getenv('CFS_INVOICE_PREFIX', 'REG') @@ -195,11 +195,7 @@ class _Config(object): # pylint: disable=too-few-public-methods EFT_TRANSFER_DESC = os.getenv('EFT_TRANSFER_DESC', 'BCREGISTRIES {} {} EFT TRANSFER') EFT_OVERDUE_NOTIFY_EMAILS = os.getenv('EFT_OVERDUE_NOTIFY_EMAILS', '') - # GCP PubSub - AUDIENCE = os.getenv('AUDIENCE', None) - GCP_AUTH_KEY = os.getenv('GCP_AUTH_KEY', None) - PUBLISHER_AUDIENCE = os.getenv('PUBLISHER_AUDIENCE', None) - ACCOUNT_MAILER_TOPIC = os.getenv('ACCOUNT_MAILER_TOPIC', None) + class DevConfig(_Config): # pylint: disable=too-few-public-methods diff --git a/jobs/payment-jobs/invoke_jobs.py b/jobs/payment-jobs/invoke_jobs.py index 0e46bf06c..2a6debc50 100755 --- a/jobs/payment-jobs/invoke_jobs.py +++ b/jobs/payment-jobs/invoke_jobs.py @@ -125,8 +125,12 @@ def run(job_name, argument=None): UnpaidInvoiceNotifyTask.notify_unpaid_invoices() application.logger.info('<<<< Completed Sending notification for OB invoices >>>>') case 'STATEMENTS_DUE': - StatementDueTask.process_unpaid_statements() - application.logger.info('<<<< Completed Sending notification for unpaid statements >>>>') + action_date_override = argument[0] if len(argument) >= 1 else None + auth_account_override = argument[1] if len(argument) >= 2 else None + StatementDueTask.process_unpaid_statements(action_date_override=action_date_override, + auth_account_override=auth_account_override) + application.logger.info( + '<<<< Completed Sending notification for unpaid statements >>>>') case 'ROUTING_SLIP': RoutingSlipTask.link_routing_slips() RoutingSlipTask.process_void() diff --git a/jobs/payment-jobs/poetry.lock b/jobs/payment-jobs/poetry.lock index 8f94de4d9..b29e7519e 100644 --- a/jobs/payment-jobs/poetry.lock +++ b/jobs/payment-jobs/poetry.lock @@ -1,91 +1,103 @@ -# 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 = "aiohttp" -version = "3.9.5" -description = "Async http client/server framework (asyncio)" +name = "aiohappyeyeballs" +version = "2.3.5" +description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, - {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, - {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, - {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, - {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, - {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, - {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, - {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, - {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, - {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, + {file = "aiohappyeyeballs-2.3.5-py3-none-any.whl", hash = "sha256:4d6dea59215537dbc746e93e779caea8178c866856a721c9c660d7a5a7b8be03"}, + {file = "aiohappyeyeballs-2.3.5.tar.gz", hash = "sha256:6fa48b9f1317254f122a07a131a86b71ca6946ca989ce6326fff54a99a920105"}, ] -[package.dependencies] +[[package]] +name = "aiohttp" +version = "3.10.3" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc36cbdedf6f259371dbbbcaae5bb0e95b879bc501668ab6306af867577eb5db"}, + {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85466b5a695c2a7db13eb2c200af552d13e6a9313d7fa92e4ffe04a2c0ea74c1"}, + {file = "aiohttp-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:71bb1d97bfe7e6726267cea169fdf5df7658831bb68ec02c9c6b9f3511e108bb"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baec1eb274f78b2de54471fc4c69ecbea4275965eab4b556ef7a7698dee18bf2"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13031e7ec1188274bad243255c328cc3019e36a5a907978501256000d57a7201"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bbc55a964b8eecb341e492ae91c3bd0848324d313e1e71a27e3d96e6ee7e8e8"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8cc0564b286b625e673a2615ede60a1704d0cbbf1b24604e28c31ed37dc62aa"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f817a54059a4cfbc385a7f51696359c642088710e731e8df80d0607193ed2b73"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8542c9e5bcb2bd3115acdf5adc41cda394e7360916197805e7e32b93d821ef93"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:671efce3a4a0281060edf9a07a2f7e6230dca3a1cbc61d110eee7753d28405f7"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0974f3b5b0132edcec92c3306f858ad4356a63d26b18021d859c9927616ebf27"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:44bb159b55926b57812dca1b21c34528e800963ffe130d08b049b2d6b994ada7"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6ae9ae382d1c9617a91647575255ad55a48bfdde34cc2185dd558ce476bf16e9"}, + {file = "aiohttp-3.10.3-cp310-cp310-win32.whl", hash = "sha256:aed12a54d4e1ee647376fa541e1b7621505001f9f939debf51397b9329fd88b9"}, + {file = "aiohttp-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:b51aef59370baf7444de1572f7830f59ddbabd04e5292fa4218d02f085f8d299"}, + {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e021c4c778644e8cdc09487d65564265e6b149896a17d7c0f52e9a088cc44e1b"}, + {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:24fade6dae446b183e2410a8628b80df9b7a42205c6bfc2eff783cbeedc224a2"}, + {file = "aiohttp-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bc8e9f15939dacb0e1f2d15f9c41b786051c10472c7a926f5771e99b49a5957f"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5a9ec959b5381271c8ec9310aae1713b2aec29efa32e232e5ef7dcca0df0279"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a5d0ea8a6467b15d53b00c4e8ea8811e47c3cc1bdbc62b1aceb3076403d551f"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9ed607dbbdd0d4d39b597e5bf6b0d40d844dfb0ac6a123ed79042ef08c1f87e"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e66d5b506832e56add66af88c288c1d5ba0c38b535a1a59e436b300b57b23e"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fda91ad797e4914cca0afa8b6cccd5d2b3569ccc88731be202f6adce39503189"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:61ccb867b2f2f53df6598eb2a93329b5eee0b00646ee79ea67d68844747a418e"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d881353264e6156f215b3cb778c9ac3184f5465c2ece5e6fce82e68946868ef"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b031ce229114825f49cec4434fa844ccb5225e266c3e146cb4bdd025a6da52f1"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5337cc742a03f9e3213b097abff8781f79de7190bbfaa987bd2b7ceb5bb0bdec"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab3361159fd3dcd0e48bbe804006d5cfb074b382666e6c064112056eb234f1a9"}, + {file = "aiohttp-3.10.3-cp311-cp311-win32.whl", hash = "sha256:05d66203a530209cbe40f102ebaac0b2214aba2a33c075d0bf825987c36f1f0b"}, + {file = "aiohttp-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:70b4a4984a70a2322b70e088d654528129783ac1ebbf7dd76627b3bd22db2f17"}, + {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:166de65e2e4e63357cfa8417cf952a519ac42f1654cb2d43ed76899e2319b1ee"}, + {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7084876352ba3833d5d214e02b32d794e3fd9cf21fdba99cff5acabeb90d9806"}, + {file = "aiohttp-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d98c604c93403288591d7d6d7d6cc8a63459168f8846aeffd5b3a7f3b3e5e09"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d73b073a25a0bb8bf014345374fe2d0f63681ab5da4c22f9d2025ca3e3ea54fc"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8da6b48c20ce78f5721068f383e0e113dde034e868f1b2f5ee7cb1e95f91db57"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a9dcdccf50284b1b0dc72bc57e5bbd3cc9bf019060dfa0668f63241ccc16aa7"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56fb94bae2be58f68d000d046172d8b8e6b1b571eb02ceee5535e9633dcd559c"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf75716377aad2c718cdf66451c5cf02042085d84522aec1f9246d3e4b8641a6"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c51ed03e19c885c8e91f574e4bbe7381793f56f93229731597e4a499ffef2a5"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b84857b66fa6510a163bb083c1199d1ee091a40163cfcbbd0642495fed096204"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c124b9206b1befe0491f48185fd30a0dd51b0f4e0e7e43ac1236066215aff272"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3461d9294941937f07bbbaa6227ba799bc71cc3b22c40222568dc1cca5118f68"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08bd0754d257b2db27d6bab208c74601df6f21bfe4cb2ec7b258ba691aac64b3"}, + {file = "aiohttp-3.10.3-cp312-cp312-win32.whl", hash = "sha256:7f9159ae530297f61a00116771e57516f89a3de6ba33f314402e41560872b50a"}, + {file = "aiohttp-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:e1128c5d3a466279cb23c4aa32a0f6cb0e7d2961e74e9e421f90e74f75ec1edf"}, + {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d1100e68e70eb72eadba2b932b185ebf0f28fd2f0dbfe576cfa9d9894ef49752"}, + {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a541414578ff47c0a9b0b8b77381ea86b0c8531ab37fc587572cb662ccd80b88"}, + {file = "aiohttp-3.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d5548444ef60bf4c7b19ace21f032fa42d822e516a6940d36579f7bfa8513f9c"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba2e838b5e6a8755ac8297275c9460e729dc1522b6454aee1766c6de6d56e5e"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48665433bb59144aaf502c324694bec25867eb6630fcd831f7a893ca473fcde4"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bac352fceed158620ce2d701ad39d4c1c76d114255a7c530e057e2b9f55bdf9f"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0f670502100cdc567188c49415bebba947eb3edaa2028e1a50dd81bd13363f"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b09f38a67679e32d380fe512189ccb0b25e15afc79b23fbd5b5e48e4fc8fd9"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:cd788602e239ace64f257d1c9d39898ca65525583f0fbf0988bcba19418fe93f"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:214277dcb07ab3875f17ee1c777d446dcce75bea85846849cc9d139ab8f5081f"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:32007fdcaab789689c2ecaaf4b71f8e37bf012a15cd02c0a9db8c4d0e7989fa8"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:123e5819bfe1b87204575515cf448ab3bf1489cdeb3b61012bde716cda5853e7"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:812121a201f0c02491a5db335a737b4113151926a79ae9ed1a9f41ea225c0e3f"}, + {file = "aiohttp-3.10.3-cp38-cp38-win32.whl", hash = "sha256:b97dc9a17a59f350c0caa453a3cb35671a2ffa3a29a6ef3568b523b9113d84e5"}, + {file = "aiohttp-3.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:3731a73ddc26969d65f90471c635abd4e1546a25299b687e654ea6d2fc052394"}, + {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38d91b98b4320ffe66efa56cb0f614a05af53b675ce1b8607cdb2ac826a8d58e"}, + {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9743fa34a10a36ddd448bba8a3adc2a66a1c575c3c2940301bacd6cc896c6bf1"}, + {file = "aiohttp-3.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7c126f532caf238031c19d169cfae3c6a59129452c990a6e84d6e7b198a001dc"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:926e68438f05703e500b06fe7148ef3013dd6f276de65c68558fa9974eeb59ad"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:434b3ab75833accd0b931d11874e206e816f6e6626fd69f643d6a8269cd9166a"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d35235a44ec38109b811c3600d15d8383297a8fab8e3dec6147477ec8636712a"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59c489661edbd863edb30a8bd69ecb044bd381d1818022bc698ba1b6f80e5dd1"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50544fe498c81cb98912afabfc4e4d9d85e89f86238348e3712f7ca6a2f01dab"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:09bc79275737d4dc066e0ae2951866bb36d9c6b460cb7564f111cc0427f14844"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:af4dbec58e37f5afff4f91cdf235e8e4b0bd0127a2a4fd1040e2cad3369d2f06"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b22cae3c9dd55a6b4c48c63081d31c00fc11fa9db1a20c8a50ee38c1a29539d2"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ba562736d3fbfe9241dad46c1a8994478d4a0e50796d80e29d50cabe8fbfcc3f"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f25d6c4e82d7489be84f2b1c8212fafc021b3731abdb61a563c90e37cced3a21"}, + {file = "aiohttp-3.10.3-cp39-cp39-win32.whl", hash = "sha256:b69d832e5f5fa15b1b6b2c8eb6a9fd2c0ec1fd7729cb4322ed27771afc9fc2ac"}, + {file = "aiohttp-3.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:673bb6e3249dc8825df1105f6ef74e2eab779b7ff78e96c15cadb78b04a83752"}, + {file = "aiohttp-3.10.3.tar.gz", hash = "sha256:21650e7032cc2d31fc23d353d7123e771354f2a3d5b05a5647fc30fea214e696"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" aiosignal = ">=1.1.2" attrs = ">=17.3.0" frozenlist = ">=1.1.1" @@ -93,7 +105,7 @@ multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "brotlicffi"] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] [[package]] name = "aiosignal" @@ -198,13 +210,13 @@ files = [ [[package]] name = "astroid" -version = "3.1.0" +version = "3.2.4" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.8.0" files = [ - {file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"}, - {file = "astroid-3.1.0.tar.gz", hash = "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"}, + {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"}, + {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, ] [[package]] @@ -228,52 +240,52 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "autopep8" -version = "2.1.0" +version = "2.3.1" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" optional = false python-versions = ">=3.8" files = [ - {file = "autopep8-2.1.0-py2.py3-none-any.whl", hash = "sha256:2bb76888c5edbcafe6aabab3c47ba534f5a2c2d245c2eddced4a30c4b4946357"}, - {file = "autopep8-2.1.0.tar.gz", hash = "sha256:1fa8964e4618929488f4ec36795c7ff12924a68b8bf01366c094fc52f770b6e7"}, + {file = "autopep8-2.3.1-py2.py3-none-any.whl", hash = "sha256:a203fe0fcad7939987422140ab17a930f684763bf7335bdb6709991dd7ef6c2d"}, + {file = "autopep8-2.3.1.tar.gz", hash = "sha256:8d6c87eba648fdcfc83e29b788910b8643171c395d9c4bcf115ece035b9c9dda"}, ] [package.dependencies] -pycodestyle = ">=2.11.0" +pycodestyle = ">=2.12.0" [[package]] name = "bcrypt" -version = "4.1.2" +version = "4.2.0" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.7" files = [ - {file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0"}, - {file = "bcrypt-4.1.2-cp37-abi3-win32.whl", hash = "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369"}, - {file = "bcrypt-4.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551"}, - {file = "bcrypt-4.1.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a"}, - {file = "bcrypt-4.1.2-cp39-abi3-win32.whl", hash = "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f"}, - {file = "bcrypt-4.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42"}, - {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946"}, - {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d"}, - {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab"}, - {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb"}, - {file = "bcrypt-4.1.2.tar.gz", hash = "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258"}, + {file = "bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7"}, + {file = "bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458"}, + {file = "bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5"}, + {file = "bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8"}, + {file = "bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34"}, + {file = "bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9"}, + {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:39e1d30c7233cfc54f5c3f2c825156fe044efdd3e0b9d309512cc514a263ec2a"}, + {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f4f4acf526fcd1c34e7ce851147deedd4e26e6402369304220250598b26448db"}, + {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1ff39b78a52cf03fdf902635e4c81e544714861ba3f0efc56558979dd4f09170"}, + {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:373db9abe198e8e2c70d12b479464e0d5092cc122b20ec504097b5f2297ed184"}, + {file = "bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221"}, ] [package.extras] @@ -338,13 +350,13 @@ ujson = ["ujson (>=5.7.0)"] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.7.4" 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"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -537,63 +549,83 @@ files = [ [[package]] name = "coverage" -version = "7.4.4" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, - {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, - {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, - {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, - {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, - {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, - {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, - {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, - {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, - {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, - {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, - {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, - {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, - {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.extras] @@ -616,43 +648,38 @@ pytz = ">2021.1" [[package]] name = "cryptography" -version = "42.0.5" +version = "43.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, + {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, + {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, + {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, + {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, + {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, + {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, + {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, ] [package.dependencies] @@ -665,7 +692,7 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] @@ -769,13 +796,13 @@ tests = ["coverage", "coveralls", "dill", "mock", "nose"] [[package]] name = "faker" -version = "24.8.0" +version = "24.14.1" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.8" files = [ - {file = "Faker-24.8.0-py3-none-any.whl", hash = "sha256:2f70a7817b4147d67c544192e169c5653060fce8aef758db0ea8823d89caac94"}, - {file = "Faker-24.8.0.tar.gz", hash = "sha256:1a46466b22c6bf5925448f725f90c6e0d8bf085819906520ddaa15aec58a6df5"}, + {file = "Faker-24.14.1-py3-none-any.whl", hash = "sha256:a5edba3aa17a1d689c8907e5b0cd1653079c2466a4807f083aa7b5f80a00225d"}, + {file = "Faker-24.14.1.tar.gz", hash = "sha256:380a3697e696ae4fcf50a93a3d9e0286fab7dfbf05a9caa4421fa4727c6b1e89"}, ] [package.dependencies] @@ -783,18 +810,18 @@ python-dateutil = ">=2.4" [[package]] name = "flake8" -version = "7.0.0" +version = "7.1.1" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" files = [ - {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, - {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, + {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, + {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.11.0,<2.12.0" +pycodestyle = ">=2.12.0,<2.13.0" pyflakes = ">=3.2.0,<3.3.0" [[package]] @@ -908,13 +935,13 @@ Flask = "*" [[package]] name = "flask-cors" -version = "4.0.0" +version = "5.0.0" description = "A Flask extension adding a decorator for CORS support" optional = false python-versions = "*" files = [ - {file = "Flask-Cors-4.0.0.tar.gz", hash = "sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"}, - {file = "Flask_Cors-4.0.0-py2.py3-none-any.whl", hash = "sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783"}, + {file = "Flask_Cors-5.0.0-py2.py3-none-any.whl", hash = "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc"}, + {file = "flask_cors-5.0.0.tar.gz", hash = "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef"}, ] [package.dependencies] @@ -1040,13 +1067,13 @@ sqlalchemy = ">=2.0.16" [[package]] name = "freezegun" -version = "1.4.0" +version = "1.5.1" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" files = [ - {file = "freezegun-1.4.0-py3-none-any.whl", hash = "sha256:55e0fc3c84ebf0a96a5aa23ff8b53d70246479e9a68863f1fcac5a3e52f19dd6"}, - {file = "freezegun-1.4.0.tar.gz", hash = "sha256:10939b0ba0ff5adaecf3b06a5c2f73071d9678e507c5eaedb23c761d56ac774b"}, + {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, + {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, ] [package.dependencies] @@ -1157,18 +1184,18 @@ simple-cloudevent = {git = "https://github.com/daxiom/simple-cloudevent.py.git"} type = "git" url = "https://github.com/bcgov/sbc-connect-common.git" reference = "main" -resolved_reference = "c348d30e91253daef7f433a15a12379eb6b68d55" +resolved_reference = "c898988d239dc261b2b186465a1887f15512c102" subdirectory = "python/gcp-queue" [[package]] name = "google-api-core" -version = "2.17.1" +version = "2.19.1" description = "Google API client core library" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-core-2.17.1.tar.gz", hash = "sha256:9df18a1f87ee0df0bc4eea2770ebc4228392d8cc4066655b320e2cfccb15db95"}, - {file = "google_api_core-2.17.1-py3-none-any.whl", hash = "sha256:610c5b90092c360736baccf17bd3efbcb30dd380e7a6dc28a71059edb8bd0d8e"}, + {file = "google-api-core-2.19.1.tar.gz", hash = "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd"}, + {file = "google_api_core-2.19.1-py3-none-any.whl", hash = "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125"}, ] [package.dependencies] @@ -1176,7 +1203,8 @@ google-auth = ">=2.14.1,<3.0.dev0" googleapis-common-protos = ">=1.56.2,<2.0.dev0" grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +proto-plus = ">=1.22.3,<2.0.0dev" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" requests = ">=2.18.0,<3.0.0.dev0" [package.extras] @@ -1186,13 +1214,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-auth" -version = "2.29.0" +version = "2.33.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google-auth-2.29.0.tar.gz", hash = "sha256:672dff332d073227550ffc7457868ac4218d6c500b155fe6cc17d2b13602c360"}, - {file = "google_auth-2.29.0-py2.py3-none-any.whl", hash = "sha256:d452ad095688cd52bae0ad6fafe027f6a6d6f560e810fec20914e17a09526415"}, + {file = "google_auth-2.33.0-py2.py3-none-any.whl", hash = "sha256:8eff47d0d4a34ab6265c50a106a3362de6a9975bb08998700e389f857e4d39df"}, + {file = "google_auth-2.33.0.tar.gz", hash = "sha256:d6a52342160d7290e334b4d47ba390767e4438ad0d45b7630774533e82655b95"}, ] [package.dependencies] @@ -1209,13 +1237,13 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "google-cloud-pubsub" -version = "2.21.2" +version = "2.23.0" description = "Google Cloud Pub/Sub API client library" optional = false python-versions = ">=3.7" files = [ - {file = "google-cloud-pubsub-2.21.2.tar.gz", hash = "sha256:fc72226b14731db2873f7c4031cc757e274bbcdabcac7523b2cd6e46130d6096"}, - {file = "google_cloud_pubsub-2.21.2-py2.py3-none-any.whl", hash = "sha256:05a6b01e5bda6f4a4858700e3e9a12e3080589718d648b2383e5818131db9ce4"}, + {file = "google_cloud_pubsub-2.23.0-py2.py3-none-any.whl", hash = "sha256:d341b2df8b105d0e3774b4bc9173bc0cf26bced31a4736cd9eefa33453b75dff"}, + {file = "google_cloud_pubsub-2.23.0.tar.gz", hash = "sha256:cf3d6f2ab11b5c8dfc0aa7d4cae5ee1d66b408d9666f1762c9c17e269cd8b658"}, ] [package.dependencies] @@ -1225,25 +1253,25 @@ grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" grpcio = ">=1.51.3,<2.0dev" grpcio-status = ">=1.33.2" proto-plus = {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" +protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" [package.extras] libcst = ["libcst (>=0.3.10)"] [[package]] name = "googleapis-common-protos" -version = "1.63.0" +version = "1.63.2" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.63.0.tar.gz", hash = "sha256:17ad01b11d5f1d0171c06d3ba5c04c54474e883b66b949722b4938ee2694ef4e"}, - {file = "googleapis_common_protos-1.63.0-py2.py3-none-any.whl", hash = "sha256:ae45f75702f7c08b541f750854a678bd8f534a1a6bace6afe975f1d0a82d6632"}, + {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, + {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, ] [package.dependencies] grpcio = {version = ">=1.44.0,<2.0.0.dev0", optional = true, markers = "extra == \"grpc\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] @@ -1321,120 +1349,113 @@ test = ["objgraph", "psutil"] [[package]] name = "grpc-google-iam-v1" -version = "0.13.0" +version = "0.13.1" description = "IAM API client library" optional = false python-versions = ">=3.7" files = [ - {file = "grpc-google-iam-v1-0.13.0.tar.gz", hash = "sha256:fad318608b9e093258fbf12529180f400d1c44453698a33509cc6ecf005b294e"}, - {file = "grpc_google_iam_v1-0.13.0-py2.py3-none-any.whl", hash = "sha256:53902e2af7de8df8c1bd91373d9be55b0743ec267a7428ea638db3775becae89"}, + {file = "grpc-google-iam-v1-0.13.1.tar.gz", hash = "sha256:3ff4b2fd9d990965e410965253c0da6f66205d5a8291c4c31c6ebecca18a9001"}, + {file = "grpc_google_iam_v1-0.13.1-py2.py3-none-any.whl", hash = "sha256:c3e86151a981811f30d5e7330f271cee53e73bb87755e88cc3b6f0c7b5fe374e"}, ] [package.dependencies] googleapis-common-protos = {version = ">=1.56.0,<2.0.0dev", extras = ["grpc"]} grpcio = ">=1.44.0,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" [[package]] name = "grpcio" -version = "1.62.1" +version = "1.65.4" description = "HTTP/2-based RPC framework" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "grpcio-1.62.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:179bee6f5ed7b5f618844f760b6acf7e910988de77a4f75b95bbfaa8106f3c1e"}, - {file = "grpcio-1.62.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:48611e4fa010e823ba2de8fd3f77c1322dd60cb0d180dc6630a7e157b205f7ea"}, - {file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:b2a0e71b0a2158aa4bce48be9f8f9eb45cbd17c78c7443616d00abbe2a509f6d"}, - {file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbe80577c7880911d3ad65e5ecc997416c98f354efeba2f8d0f9112a67ed65a5"}, - {file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58f6c693d446964e3292425e1d16e21a97a48ba9172f2d0df9d7b640acb99243"}, - {file = "grpcio-1.62.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:77c339403db5a20ef4fed02e4d1a9a3d9866bf9c0afc77a42234677313ea22f3"}, - {file = "grpcio-1.62.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b5a4ea906db7dec694098435d84bf2854fe158eb3cd51e1107e571246d4d1d70"}, - {file = "grpcio-1.62.1-cp310-cp310-win32.whl", hash = "sha256:4187201a53f8561c015bc745b81a1b2d278967b8de35f3399b84b0695e281d5f"}, - {file = "grpcio-1.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:844d1f3fb11bd1ed362d3fdc495d0770cfab75761836193af166fee113421d66"}, - {file = "grpcio-1.62.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:833379943d1728a005e44103f17ecd73d058d37d95783eb8f0b28ddc1f54d7b2"}, - {file = "grpcio-1.62.1-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:c7fcc6a32e7b7b58f5a7d27530669337a5d587d4066060bcb9dee7a8c833dfb7"}, - {file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:fa7d28eb4d50b7cbe75bb8b45ed0da9a1dc5b219a0af59449676a29c2eed9698"}, - {file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48f7135c3de2f298b833be8b4ae20cafe37091634e91f61f5a7eb3d61ec6f660"}, - {file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f11fd63365ade276c9d4a7b7df5c136f9030e3457107e1791b3737a9b9ed6a"}, - {file = "grpcio-1.62.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4b49fd8fe9f9ac23b78437da94c54aa7e9996fbb220bac024a67469ce5d0825f"}, - {file = "grpcio-1.62.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:482ae2ae78679ba9ed5752099b32e5fe580443b4f798e1b71df412abf43375db"}, - {file = "grpcio-1.62.1-cp311-cp311-win32.whl", hash = "sha256:1faa02530b6c7426404372515fe5ddf66e199c2ee613f88f025c6f3bd816450c"}, - {file = "grpcio-1.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bd90b8c395f39bc82a5fb32a0173e220e3f401ff697840f4003e15b96d1befc"}, - {file = "grpcio-1.62.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:b134d5d71b4e0837fff574c00e49176051a1c532d26c052a1e43231f252d813b"}, - {file = "grpcio-1.62.1-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d1f6c96573dc09d50dbcbd91dbf71d5cf97640c9427c32584010fbbd4c0e0037"}, - {file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:359f821d4578f80f41909b9ee9b76fb249a21035a061a327f91c953493782c31"}, - {file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a485f0c2010c696be269184bdb5ae72781344cb4e60db976c59d84dd6354fac9"}, - {file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b50b09b4dc01767163d67e1532f948264167cd27f49e9377e3556c3cba1268e1"}, - {file = "grpcio-1.62.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3227c667dccbe38f2c4d943238b887bac588d97c104815aecc62d2fd976e014b"}, - {file = "grpcio-1.62.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3952b581eb121324853ce2b191dae08badb75cd493cb4e0243368aa9e61cfd41"}, - {file = "grpcio-1.62.1-cp312-cp312-win32.whl", hash = "sha256:83a17b303425104d6329c10eb34bba186ffa67161e63fa6cdae7776ff76df73f"}, - {file = "grpcio-1.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:6696ffe440333a19d8d128e88d440f91fb92c75a80ce4b44d55800e656a3ef1d"}, - {file = "grpcio-1.62.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:e3393b0823f938253370ebef033c9fd23d27f3eae8eb9a8f6264900c7ea3fb5a"}, - {file = "grpcio-1.62.1-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:83e7ccb85a74beaeae2634f10eb858a0ed1a63081172649ff4261f929bacfd22"}, - {file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:882020c87999d54667a284c7ddf065b359bd00251fcd70279ac486776dbf84ec"}, - {file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a10383035e864f386fe096fed5c47d27a2bf7173c56a6e26cffaaa5a361addb1"}, - {file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:960edebedc6b9ada1ef58e1c71156f28689978188cd8cff3b646b57288a927d9"}, - {file = "grpcio-1.62.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:23e2e04b83f347d0aadde0c9b616f4726c3d76db04b438fd3904b289a725267f"}, - {file = "grpcio-1.62.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978121758711916d34fe57c1f75b79cdfc73952f1481bb9583399331682d36f7"}, - {file = "grpcio-1.62.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9084086190cc6d628f282e5615f987288b95457292e969b9205e45b442276407"}, - {file = "grpcio-1.62.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:22bccdd7b23c420a27fd28540fb5dcbc97dc6be105f7698cb0e7d7a420d0e362"}, - {file = "grpcio-1.62.1-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:8999bf1b57172dbc7c3e4bb3c732658e918f5c333b2942243f10d0d653953ba9"}, - {file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:d9e52558b8b8c2f4ac05ac86344a7417ccdd2b460a59616de49eb6933b07a0bd"}, - {file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1714e7bc935780bc3de1b3fcbc7674209adf5208ff825799d579ffd6cd0bd505"}, - {file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8842ccbd8c0e253c1f189088228f9b433f7a93b7196b9e5b6f87dba393f5d5d"}, - {file = "grpcio-1.62.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f1e7b36bdff50103af95a80923bf1853f6823dd62f2d2a2524b66ed74103e49"}, - {file = "grpcio-1.62.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bba97b8e8883a8038606480d6b6772289f4c907f6ba780fa1f7b7da7dfd76f06"}, - {file = "grpcio-1.62.1-cp38-cp38-win32.whl", hash = "sha256:a7f615270fe534548112a74e790cd9d4f5509d744dd718cd442bf016626c22e4"}, - {file = "grpcio-1.62.1-cp38-cp38-win_amd64.whl", hash = "sha256:e6c8c8693df718c5ecbc7babb12c69a4e3677fd11de8886f05ab22d4e6b1c43b"}, - {file = "grpcio-1.62.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:73db2dc1b201d20ab7083e7041946910bb991e7e9761a0394bbc3c2632326483"}, - {file = "grpcio-1.62.1-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:407b26b7f7bbd4f4751dbc9767a1f0716f9fe72d3d7e96bb3ccfc4aace07c8de"}, - {file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:f8de7c8cef9261a2d0a62edf2ccea3d741a523c6b8a6477a340a1f2e417658de"}, - {file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd5c8a1af40ec305d001c60236308a67e25419003e9bb3ebfab5695a8d0b369"}, - {file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be0477cb31da67846a33b1a75c611f88bfbcd427fe17701b6317aefceee1b96f"}, - {file = "grpcio-1.62.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:60dcd824df166ba266ee0cfaf35a31406cd16ef602b49f5d4dfb21f014b0dedd"}, - {file = "grpcio-1.62.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:973c49086cabab773525f6077f95e5a993bfc03ba8fc32e32f2c279497780585"}, - {file = "grpcio-1.62.1-cp39-cp39-win32.whl", hash = "sha256:12859468e8918d3bd243d213cd6fd6ab07208195dc140763c00dfe901ce1e1b4"}, - {file = "grpcio-1.62.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7209117bbeebdfa5d898205cc55153a51285757902dd73c47de498ad4d11332"}, - {file = "grpcio-1.62.1.tar.gz", hash = "sha256:6c455e008fa86d9e9a9d85bb76da4277c0d7d9668a3bfa70dbe86e9f3c759947"}, + {file = "grpcio-1.65.4-cp310-cp310-linux_armv7l.whl", hash = "sha256:0e85c8766cf7f004ab01aff6a0393935a30d84388fa3c58d77849fcf27f3e98c"}, + {file = "grpcio-1.65.4-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:e4a795c02405c7dfa8affd98c14d980f4acea16ea3b539e7404c645329460e5a"}, + {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d7b984a8dd975d949c2042b9b5ebcf297d6d5af57dcd47f946849ee15d3c2fb8"}, + {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644a783ce604a7d7c91412bd51cf9418b942cf71896344b6dc8d55713c71ce82"}, + {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5764237d751d3031a36fafd57eb7d36fd2c10c658d2b4057c516ccf114849a3e"}, + {file = "grpcio-1.65.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ee40d058cf20e1dd4cacec9c39e9bce13fedd38ce32f9ba00f639464fcb757de"}, + {file = "grpcio-1.65.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4482a44ce7cf577a1f8082e807a5b909236bce35b3e3897f839f2fbd9ae6982d"}, + {file = "grpcio-1.65.4-cp310-cp310-win32.whl", hash = "sha256:66bb051881c84aa82e4f22d8ebc9d1704b2e35d7867757f0740c6ef7b902f9b1"}, + {file = "grpcio-1.65.4-cp310-cp310-win_amd64.whl", hash = "sha256:870370524eff3144304da4d1bbe901d39bdd24f858ce849b7197e530c8c8f2ec"}, + {file = "grpcio-1.65.4-cp311-cp311-linux_armv7l.whl", hash = "sha256:85e9c69378af02e483bc626fc19a218451b24a402bdf44c7531e4c9253fb49ef"}, + {file = "grpcio-1.65.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2bd672e005afab8bf0d6aad5ad659e72a06dd713020554182a66d7c0c8f47e18"}, + {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:abccc5d73f5988e8f512eb29341ed9ced923b586bb72e785f265131c160231d8"}, + {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:886b45b29f3793b0c2576201947258782d7e54a218fe15d4a0468d9a6e00ce17"}, + {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be952436571dacc93ccc7796db06b7daf37b3b56bb97e3420e6503dccfe2f1b4"}, + {file = "grpcio-1.65.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8dc9ddc4603ec43f6238a5c95400c9a901b6d079feb824e890623da7194ff11e"}, + {file = "grpcio-1.65.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ade1256c98cba5a333ef54636095f2c09e6882c35f76acb04412f3b1aa3c29a5"}, + {file = "grpcio-1.65.4-cp311-cp311-win32.whl", hash = "sha256:280e93356fba6058cbbfc6f91a18e958062ef1bdaf5b1caf46c615ba1ae71b5b"}, + {file = "grpcio-1.65.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2b819f9ee27ed4e3e737a4f3920e337e00bc53f9e254377dd26fc7027c4d558"}, + {file = "grpcio-1.65.4-cp312-cp312-linux_armv7l.whl", hash = "sha256:926a0750a5e6fb002542e80f7fa6cab8b1a2ce5513a1c24641da33e088ca4c56"}, + {file = "grpcio-1.65.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2a1d4c84d9e657f72bfbab8bedf31bdfc6bfc4a1efb10b8f2d28241efabfaaf2"}, + {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:17de4fda50967679677712eec0a5c13e8904b76ec90ac845d83386b65da0ae1e"}, + {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dee50c1b69754a4228e933696408ea87f7e896e8d9797a3ed2aeed8dbd04b74"}, + {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74c34fc7562bdd169b77966068434a93040bfca990e235f7a67cdf26e1bd5c63"}, + {file = "grpcio-1.65.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:24a2246e80a059b9eb981e4c2a6d8111b1b5e03a44421adbf2736cc1d4988a8a"}, + {file = "grpcio-1.65.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18c10f0d054d2dce34dd15855fcca7cc44ec3b811139437543226776730c0f28"}, + {file = "grpcio-1.65.4-cp312-cp312-win32.whl", hash = "sha256:d72962788b6c22ddbcdb70b10c11fbb37d60ae598c51eb47ec019db66ccfdff0"}, + {file = "grpcio-1.65.4-cp312-cp312-win_amd64.whl", hash = "sha256:7656376821fed8c89e68206a522522317787a3d9ed66fb5110b1dff736a5e416"}, + {file = "grpcio-1.65.4-cp38-cp38-linux_armv7l.whl", hash = "sha256:4934077b33aa6fe0b451de8b71dabde96bf2d9b4cb2b3187be86e5adebcba021"}, + {file = "grpcio-1.65.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0cef8c919a3359847c357cb4314e50ed1f0cca070f828ee8f878d362fd744d52"}, + {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:a925446e6aa12ca37114840d8550f308e29026cdc423a73da3043fd1603a6385"}, + {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf53e6247f1e2af93657e62e240e4f12e11ee0b9cef4ddcb37eab03d501ca864"}, + {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdb34278e4ceb224c89704cd23db0d902e5e3c1c9687ec9d7c5bb4c150f86816"}, + {file = "grpcio-1.65.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e6cbdd107e56bde55c565da5fd16f08e1b4e9b0674851d7749e7f32d8645f524"}, + {file = "grpcio-1.65.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:626319a156b1f19513156a3b0dbfe977f5f93db63ca673a0703238ebd40670d7"}, + {file = "grpcio-1.65.4-cp38-cp38-win32.whl", hash = "sha256:3d1bbf7e1dd1096378bd83c83f554d3b93819b91161deaf63e03b7022a85224a"}, + {file = "grpcio-1.65.4-cp38-cp38-win_amd64.whl", hash = "sha256:a99e6dffefd3027b438116f33ed1261c8d360f0dd4f943cb44541a2782eba72f"}, + {file = "grpcio-1.65.4-cp39-cp39-linux_armv7l.whl", hash = "sha256:874acd010e60a2ec1e30d5e505b0651ab12eb968157cd244f852b27c6dbed733"}, + {file = "grpcio-1.65.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b07f36faf01fca5427d4aa23645e2d492157d56c91fab7e06fe5697d7e171ad4"}, + {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:b81711bf4ec08a3710b534e8054c7dcf90f2edc22bebe11c1775a23f145595fe"}, + {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88fcabc332a4aef8bcefadc34a02e9ab9407ab975d2c7d981a8e12c1aed92aa1"}, + {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9ba3e63108a8749994f02c7c0e156afb39ba5bdf755337de8e75eb685be244b"}, + {file = "grpcio-1.65.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8eb485801957a486bf5de15f2c792d9f9c897a86f2f18db8f3f6795a094b4bb2"}, + {file = "grpcio-1.65.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075f3903bc1749ace93f2b0664f72964ee5f2da5c15d4b47e0ab68e4f442c257"}, + {file = "grpcio-1.65.4-cp39-cp39-win32.whl", hash = "sha256:0a0720299bdb2cc7306737295d56e41ce8827d5669d4a3cd870af832e3b17c4d"}, + {file = "grpcio-1.65.4-cp39-cp39-win_amd64.whl", hash = "sha256:a146bc40fa78769f22e1e9ff4f110ef36ad271b79707577bf2a31e3e931141b9"}, + {file = "grpcio-1.65.4.tar.gz", hash = "sha256:2a4f476209acffec056360d3e647ae0e14ae13dcf3dfb130c227ae1c594cbe39"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.62.1)"] +protobuf = ["grpcio-tools (>=1.65.4)"] [[package]] name = "grpcio-status" -version = "1.62.1" +version = "1.62.3" description = "Status proto mapping for gRPC" optional = false python-versions = ">=3.6" files = [ - {file = "grpcio-status-1.62.1.tar.gz", hash = "sha256:3431c8abbab0054912c41df5c72f03ddf3b7a67be8a287bb3c18a3456f96ff77"}, - {file = "grpcio_status-1.62.1-py3-none-any.whl", hash = "sha256:af0c3ab85da31669f21749e8d53d669c061ebc6ce5637be49a46edcb7aa8ab17"}, + {file = "grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485"}, + {file = "grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8"}, ] [package.dependencies] googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.62.1" +grpcio = ">=1.62.3" protobuf = ">=4.21.6" [[package]] name = "gunicorn" -version = "21.2.0" +version = "22.0.0" description = "WSGI HTTP Server for UNIX" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, - {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, + {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"}, + {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"}, ] [package.dependencies] packaging = "*" [package.extras] -eventlet = ["eventlet (>=0.24.1)"] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] gevent = ["gevent (>=1.4.0)"] setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] tornado = ["tornado (>=0.2)"] [[package]] @@ -1453,13 +1474,13 @@ python-dateutil = "*" [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -1519,13 +1540,13 @@ tests = ["codecov", "coverage", "flake8", "flake8-quotes", "flake8-typing-import [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -1744,13 +1765,13 @@ files = [ [[package]] name = "minio" -version = "7.2.5" +version = "7.2.7" description = "MinIO Python SDK for Amazon S3 Compatible Cloud Storage" optional = false python-versions = "*" files = [ - {file = "minio-7.2.5-py3-none-any.whl", hash = "sha256:ed9176c96d4271cb1022b9ecb8a538b1e55b32ae06add6de16425cab99ef2304"}, - {file = "minio-7.2.5.tar.gz", hash = "sha256:59d8906e2da248a9caac34d4958a859cc3a44abbe6447910c82b5abfa9d6a2e1"}, + {file = "minio-7.2.7-py3-none-any.whl", hash = "sha256:59d1f255d852fe7104018db75b3bebbd987e538690e680f7c5de835e422de837"}, + {file = "minio-7.2.7.tar.gz", hash = "sha256:473d5d53d79f340f3cd632054d0c82d2f93177ce1af2eac34a235bea55708d98"}, ] [package.dependencies] @@ -1762,13 +1783,13 @@ urllib3 = "*" [[package]] name = "more-itertools" -version = "10.2.0" +version = "10.4.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.8" files = [ - {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, - {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, + {file = "more-itertools-10.4.0.tar.gz", hash = "sha256:fe0e63c4ab068eac62410ab05cccca2dc71ec44ba8ef29916a0090df061cf923"}, + {file = "more_itertools-10.4.0-py3-none-any.whl", hash = "sha256:0f7d9f83a0a8dcfa8a2694a770590d98a67ea943e3d9f5298309a484758c4e27"}, ] [[package]] @@ -1896,13 +1917,13 @@ files = [ [[package]] name = "paramiko" -version = "3.4.0" +version = "3.4.1" description = "SSH2 protocol library" optional = false python-versions = ">=3.6" files = [ - {file = "paramiko-3.4.0-py3-none-any.whl", hash = "sha256:43f0b51115a896f9c00f59618023484cb3a14b98bbceab43394a39c6739b7ee7"}, - {file = "paramiko-3.4.0.tar.gz", hash = "sha256:aac08f26a31dc4dffd92821527d1682d99d52f9ef6851968114a8728f3c274d3"}, + {file = "paramiko-3.4.1-py3-none-any.whl", hash = "sha256:8e49fd2f82f84acf7ffd57c64311aa2b30e575370dc23bdb375b10262f7eac32"}, + {file = "paramiko-3.4.1.tar.gz", hash = "sha256:8b15302870af7f6652f2e038975c1d2973f06046cb5d7d65355668b3ecbece0c"}, ] [package.dependencies] @@ -1932,18 +1953,18 @@ blinker = "1.7.0" cachelib = "0.9.0" cachetools = "5.3.3" cattrs = "23.2.3" -certifi = "2024.2.2" +certifi = "2024.7.4" cffi = "1.16.0" charset-normalizer = "3.3.2" click = "8.1.7" croniter = "2.0.2" -cryptography = "42.0.5" +cryptography = "43.0.1" dpath = "2.1.6" ecdsa = "0.18.0" expiringdict = "1.2.2" flask = "3.0.2" flask-caching = "2.3.0" -flask-cors = "4.0.0" +flask-cors = "5.0.0" flask-jwt-oidc = {git = "https://github.com/seeker25/flask-jwt-oidc.git"} flask-marshmallow = "1.2.0" flask-migrate = "4.0.7" @@ -1952,12 +1973,12 @@ flask-script = "2.0.6" flask-sqlalchemy = "3.1.1" gcp-queue = {git = "https://github.com/bcgov/sbc-connect-common.git", branch = "main", subdirectory = "python/gcp-queue"} greenlet = "3.0.3" -gunicorn = "21.2.0" +gunicorn = "22.0.0" holidays = "0.37" -idna = "3.6" +idna = "3.7" itsdangerous = "2.1.2" jaeger-client = "4.8.0" -jinja2 = "3.1.3" +jinja2 = "3.1.4" jsonschema = "4.17.3" launchdarkly-eventsource = "1.1.1" launchdarkly-server-sdk = "8.2.1" @@ -1985,23 +2006,24 @@ requests = "2.32.2" rsa = "4.9" sbc-common-components = {git = "https://github.com/bcgov/sbc-common-components.git", subdirectory = "python"} semver = "3.0.2" -sentry-sdk = "1.41.0" +sentry-sdk = {version = "^2.8.0", extras = ["flask"]} +setuptools = "^73.0.1" six = "1.16.0" -sql-versioning = {git = "https://github.com/bcgov/lear.git", branch = "feature-legal-name", subdirectory = "python/common/sql-versioning"} +sql-versioning = {git = "https://github.com/bcgov/sbc-connect-common.git", branch = "main", subdirectory = "python/sql-versioning"} sqlalchemy = "2.0.28" sqlalchemy-utils = "0.41.1" threadloop = "1.0.2" thrift = "0.16.0" -tornado = "6.4" +tornado = "6.4.1" typing-extensions = "4.10.0" -urllib3 = "2.2.1" -werkzeug = "3.0.1" +urllib3 = "2.2.2" +werkzeug = "3.0.3" [package.source] type = "git" url = "https://github.com/seeker25/sbc-pay.git" -reference = "20596_fixes" -resolved_reference = "04a584f49ee4b4389d6f316d16e05aaf756eafb5" +reference = "21519" +resolved_reference = "9f02a2635064d57958d1b6a8abcbee5d1f42bf3a" subdirectory = "pay-api" [[package]] @@ -2020,43 +2042,44 @@ flake8 = ">=5.0.0" [[package]] name = "pg8000" -version = "1.31.1" +version = "1.31.2" description = "PostgreSQL interface library" optional = false python-versions = ">=3.8" files = [ - {file = "pg8000-1.31.1-py3-none-any.whl", hash = "sha256:69aac9dba4114c9c8d0408232d54eaf7d06d271df7765caeed39960e057800e4"}, - {file = "pg8000-1.31.1.tar.gz", hash = "sha256:b11130d4c615dd3062ea8fed8143064a7978b7fe6d44f14b72261d43c8e27087"}, + {file = "pg8000-1.31.2-py3-none-any.whl", hash = "sha256:436c771ede71af4d4c22ba867a30add0bc5c942d7ab27fadbb6934a487ecc8f6"}, + {file = "pg8000-1.31.2.tar.gz", hash = "sha256:1ea46cf09d8eca07fe7eaadefd7951e37bee7fabe675df164f1a572ffb300876"}, ] [package.dependencies] python-dateutil = ">=2.8.2" -scramp = ">=1.4.4" +scramp = ">=1.4.5" [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -2208,13 +2231,13 @@ pyasn1 = ">=0.4.6,<0.6.0" [[package]] name = "pycodestyle" -version = "2.11.1" +version = "2.12.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, - {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, ] [[package]] @@ -2327,17 +2350,17 @@ files = [ [[package]] name = "pylint" -version = "3.1.0" +version = "3.2.6" description = "python code static checker" optional = false python-versions = ">=3.8.0" files = [ - {file = "pylint-3.1.0-py3-none-any.whl", hash = "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74"}, - {file = "pylint-3.1.0.tar.gz", hash = "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"}, + {file = "pylint-3.2.6-py3-none-any.whl", hash = "sha256:03c8e3baa1d9fb995b12c1dbe00aa6c4bcef210c2a2634374aedeb22fb4a8f8f"}, + {file = "pylint-3.2.6.tar.gz", hash = "sha256:a5d01678349454806cff6d886fb072294f56a58c4761278c97fb557d708e1eb3"}, ] [package.dependencies] -astroid = ">=3.1.0,<=3.2.0-dev0" +astroid = ">=3.2.4,<=3.3.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = {version = ">=0.3.7", markers = "python_version >= \"3.12\""} isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" @@ -2472,33 +2495,33 @@ paramiko = ">=1.17" [[package]] name = "pytest" -version = "8.1.1" +version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, - {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.4,<2.0" +pluggy = ">=1.5,<2" [package.extras] -testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" -version = "0.23.6" +version = "0.23.8" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, - {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, + {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, + {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, ] [package.dependencies] @@ -2663,13 +2686,13 @@ subdirectory = "python" [[package]] name = "scramp" -version = "1.4.4" +version = "1.4.5" description = "An implementation of the SCRAM protocol." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "scramp-1.4.4-py3-none-any.whl", hash = "sha256:b142312df7c2977241d951318b7ee923d6b7a4f75ba0f05b621ece1ed616faa3"}, - {file = "scramp-1.4.4.tar.gz", hash = "sha256:b7022a140040f33cf863ab2657917ed05287a807b917950489b89b9f685d59bc"}, + {file = "scramp-1.4.5-py3-none-any.whl", hash = "sha256:50e37c464fc67f37994e35bee4151e3d8f9320e9c204fca83a5d313c121bbbe7"}, + {file = "scramp-1.4.5.tar.gz", hash = "sha256:be3fbe774ca577a7a658117dca014e5d254d158cecae3dd60332dfe33ce6d78e"}, ] [package.dependencies] @@ -2688,38 +2711,47 @@ files = [ [[package]] name = "sentry-sdk" -version = "1.41.0" +version = "2.13.0" description = "Python client for Sentry (https://sentry.io)" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "sentry-sdk-1.41.0.tar.gz", hash = "sha256:4f2d6c43c07925d8cd10dfbd0970ea7cb784f70e79523cca9dbcd72df38e5a46"}, - {file = "sentry_sdk-1.41.0-py2.py3-none-any.whl", hash = "sha256:be4f8f4b29a80b6a3b71f0f31487beb9e296391da20af8504498a328befed53f"}, + {file = "sentry_sdk-2.13.0-py2.py3-none-any.whl", hash = "sha256:6beede8fc2ab4043da7f69d95534e320944690680dd9a963178a49de71d726c6"}, + {file = "sentry_sdk-2.13.0.tar.gz", hash = "sha256:8d4a576f7a98eb2fdb40e13106e41f330e5c79d72a68be1316e7852cf4995260"}, ] [package.dependencies] +blinker = {version = ">=1.1", optional = true, markers = "extra == \"flask\""} certifi = "*" -urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} +flask = {version = ">=0.11", optional = true, markers = "extra == \"flask\""} +markupsafe = {version = "*", optional = true, markers = "extra == \"flask\""} +urllib3 = ">=1.26.11" [package.extras] aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] arq = ["arq (>=0.23)"] asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] chalice = ["chalice (>=1.16.0)"] clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] -grpcio = ["grpcio (>=1.21.1)"] +grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] +huggingface-hub = ["huggingface-hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] +litestar = ["litestar (>=2.0.0)"] loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] -opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +opentelemetry-experimental = ["opentelemetry-distro"] pure-eval = ["asttokens", "executing", "pure-eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] @@ -2729,22 +2761,23 @@ sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] -tornado = ["tornado (>=5)"] +tornado = ["tornado (>=6)"] [[package]] name = "setuptools" -version = "70.3.0" +version = "73.0.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc"}, - {file = "setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5"}, + {file = "setuptools-73.0.1-py3-none-any.whl", hash = "sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e"}, + {file = "setuptools-73.0.1.tar.gz", hash = "sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193"}, ] [package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] [[package]] name = "simple-cloudevent" @@ -2797,10 +2830,10 @@ develop = false [package.source] type = "git" -url = "https://github.com/bcgov/lear.git" -reference = "feature-legal-name" -resolved_reference = "2d4c389743ff307be74af1861d6debda8bc8d194" -subdirectory = "python/common/sql-versioning" +url = "https://github.com/bcgov/sbc-connect-common.git" +reference = "HEAD" +resolved_reference = "c898988d239dc261b2b186465a1887f15512c102" +subdirectory = "python/sql-versioning" [[package]] name = "sqlalchemy" @@ -2961,33 +2994,33 @@ twisted = ["twisted"] [[package]] name = "tomlkit" -version = "0.12.4" +version = "0.13.2" description = "Style preserving TOML library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"}, - {file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"}, + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, ] [[package]] name = "tornado" -version = "6.4" +version = "6.4.1" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false -python-versions = ">= 3.8" +python-versions = ">=3.8" files = [ - {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, - {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"}, - {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"}, - {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, - {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, ] [[package]] @@ -3003,13 +3036,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" 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"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -3020,13 +3053,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "werkzeug" -version = "3.0.1" +version = "3.0.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, - {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, + {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, + {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, ] [package.dependencies] @@ -3141,4 +3174,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "0d95863f96d63bba6c213769b804eba8b6e43560623f9c806c85f4e2ff857292" +content-hash = "9e7d3948bdc3f84687c150ac4034b11992b08382a3198254065a392e85a9a13a" diff --git a/jobs/payment-jobs/pyproject.toml b/jobs/payment-jobs/pyproject.toml index 461466eca..7514b60b8 100644 --- a/jobs/payment-jobs/pyproject.toml +++ b/jobs/payment-jobs/pyproject.toml @@ -7,8 +7,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.12" -pay-api = {git = "https://github.com/seeker25/sbc-pay.git", branch = "20596_fixes", subdirectory = "pay-api"} -gunicorn = "^21.2.0" +pay-api = {git = "https://github.com/seeker25/sbc-pay.git", branch = "21519", subdirectory = "pay-api"} flask = "^3.0.2" flask-sqlalchemy = "^3.1.1" sqlalchemy = "^2.0.28" @@ -29,7 +28,6 @@ launchdarkly-server-sdk = "^8.2.1" cx-oracle = "^8.3.0" more-itertools = "^10.2.0" pg8000 = "^1.30.5" -setuptools = "^70.3.0" [tool.poetry.group.dev.dependencies] diff --git a/jobs/payment-jobs/services/routing_slip.py b/jobs/payment-jobs/services/routing_slip.py index d28d4b929..dc4f25c6c 100644 --- a/jobs/payment-jobs/services/routing_slip.py +++ b/jobs/payment-jobs/services/routing_slip.py @@ -28,8 +28,6 @@ def create_cfs_account(cfs_account: CfsAccountModel, pay_account: PaymentAccount """Create CFS account for routing slip.""" routing_slip: RoutingSlipModel = RoutingSlipModel.find_by_payment_account_id(pay_account.id) try: - # TODO add status check so that LINKED etc can be skipped. - # for RS , entity/business number=party name ; RS Number=site name cfs_account_details: Dict[str, any] = CFSService.create_cfs_account( identifier=pay_account.name, contact_info={}, diff --git a/jobs/payment-jobs/setup.cfg b/jobs/payment-jobs/setup.cfg index f4f4f4e4e..2479f675a 100755 --- a/jobs/payment-jobs/setup.cfg +++ b/jobs/payment-jobs/setup.cfg @@ -45,7 +45,7 @@ per-file-ignores = max_line_length = 120 ignore = E501 docstring-min-length=10 -notes=FIXME,XXX # TODO is ignored +notes=FIXME,XXX match_dir = tasks ignored-modules=flask_sqlalchemy sqlalchemy diff --git a/jobs/payment-jobs/tasks/activate_pad_account_task.py b/jobs/payment-jobs/tasks/activate_pad_account_task.py index 9e21bd1e8..798767187 100644 --- a/jobs/payment-jobs/tasks/activate_pad_account_task.py +++ b/jobs/payment-jobs/tasks/activate_pad_account_task.py @@ -13,7 +13,7 @@ # limitations under the License. """Task to activate accounts with pending activation.Mostly for PAD with 3 day activation period.""" -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from flask import current_app from pay_api.models import CfsAccount as CfsAccountModel @@ -47,7 +47,8 @@ def activate_pad_accounts(cls): pay_account: PaymentAccountModel = PaymentAccountModel.find_by_id(pending_account.account_id) # check is still in the pad activation period - is_activation_period_over = pay_account.pad_activation_date - timedelta(hours=1) < datetime.now() + is_activation_period_over = pay_account.pad_activation_date - timedelta(hours=1) \ + < datetime.now(tz=timezone.utc).replace(tzinfo=None) current_app.logger.info( f'Account {pay_account.id} ready for activation:{is_activation_period_over}') diff --git a/jobs/payment-jobs/tasks/ap_task.py b/jobs/payment-jobs/tasks/ap_task.py index 6290b10d4..594e44372 100644 --- a/jobs/payment-jobs/tasks/ap_task.py +++ b/jobs/payment-jobs/tasks/ap_task.py @@ -15,7 +15,7 @@ from typing import List -from datetime import date, datetime +from datetime import date, datetime, timedelta, timezone import time from flask import current_app from more_itertools import batched @@ -25,13 +25,15 @@ from pay_api.models import EjvHeader as EjvHeaderModel from pay_api.models import EjvLink as EjvLinkModel from pay_api.models import Invoice as InvoiceModel +from pay_api.models import Receipt as ReceiptModel from pay_api.models import Refund as RefundModel from pay_api.models import RoutingSlip as RoutingSlipModel from pay_api.models import db -from pay_api.utils.enums import DisbursementStatus, EjvFileType, EJVLinkType, RoutingSlipStatus +from pay_api.utils.enums import ( + DisbursementStatus, EjvFileType, EJVLinkType, InvoiceStatus, PaymentMethod, RoutingSlipStatus) +from sqlalchemy import Date, cast from tasks.common.cgi_ap import CgiAP from tasks.common.dataclasses import APLine -from tasks.ejv_partner_distribution_task import EjvPartnerDistributionTask class ApTask(CgiAP): @@ -90,7 +92,8 @@ def _create_routing_slip_refund_file(cls): # pylint:disable=too-many-locals, to for rs in routing_slips: current_app.logger.info(f'Creating refund for {rs.number}, Amount {rs.refund_amount}.') refund: RefundModel = RefundModel.find_by_routing_slip_id(rs.id) - ap_content = f'{ap_content}{cls.get_ap_header(rs.refund_amount, rs.number, datetime.now())}' + ap_content = f'{ap_content}{cls.get_ap_header(rs.refund_amount, rs.number, + datetime.now(tz=timezone.utc))}' ap_line = APLine(total=rs.refund_amount, invoice_number=rs.number, line_number=1) ap_content = f'{ap_content}{cls.get_ap_invoice_line(ap_line)}' ap_content = f'{ap_content}{cls.get_ap_address(refund.details, rs.number)}' @@ -105,13 +108,51 @@ def _create_routing_slip_refund_file(cls): # pylint:disable=too-many-locals, to cls._create_file_and_upload(ap_content) + @classmethod + def get_invoices_for_disbursement(cls, partner): + """Return invoices for disbursement. Used by EJV and AP.""" + disbursement_date = datetime.today() - timedelta(days=current_app.config.get('DISBURSEMENT_DELAY_IN_DAYS')) + invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \ + .filter(InvoiceModel.invoice_status_code == InvoiceStatus.PAID.value) \ + .filter( + InvoiceModel.payment_method_code.notin_([PaymentMethod.INTERNAL.value, + PaymentMethod.DRAWDOWN.value, + PaymentMethod.EFT.value])) \ + .filter((InvoiceModel.disbursement_status_code.is_(None)) | + (InvoiceModel.disbursement_status_code == DisbursementStatus.ERRORED.value)) \ + .filter(~InvoiceModel.receipts.any(cast(ReceiptModel.receipt_date, Date) >= disbursement_date.date())) \ + .filter(InvoiceModel.corp_type_code == partner.code) \ + .all() + current_app.logger.info(invoices) + return invoices + + @classmethod + def get_invoices_for_refund_reversal(cls, partner): + """Return invoices for refund reversal.""" + # REFUND_REQUESTED for credit card payments, CREDITED for AR and REFUNDED for other payments. + refund_inv_statuses = (InvoiceStatus.REFUNDED.value, InvoiceStatus.REFUND_REQUESTED.value, + InvoiceStatus.CREDITED.value) + + invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \ + .filter(InvoiceModel.invoice_status_code.in_(refund_inv_statuses)) \ + .filter( + InvoiceModel.payment_method_code.notin_([PaymentMethod.INTERNAL.value, + PaymentMethod.DRAWDOWN.value, + PaymentMethod.EFT.value])) \ + .filter(InvoiceModel.disbursement_status_code == DisbursementStatus.COMPLETED.value) \ + .filter(InvoiceModel.corp_type_code == partner.code) \ + .all() + current_app.logger.info(invoices) + return invoices + @classmethod def _create_non_gov_disbursement_file(cls): # pylint:disable=too-many-locals """Create AP file for disbursement for non government entities without a GL code via EFT and upload to CGI.""" cls.ap_type = EjvFileType.NON_GOV_DISBURSEMENT bca_partner = CorpTypeModel.find_by_code('BCA') - total_invoices: List[InvoiceModel] = EjvPartnerDistributionTask().get_invoices_for_disbursement(bca_partner) + \ - EjvPartnerDistributionTask().get_invoices_for_refund_reversal(bca_partner) + # TODO these two functions need to be reworked when we onboard BCA again. + total_invoices: List[InvoiceModel] = cls.get_invoices_for_disbursement(bca_partner) + \ + cls.get_invoices_for_refund_reversal(bca_partner) current_app.logger.info(f'Found {len(total_invoices)} to disburse.') if not total_invoices: diff --git a/jobs/payment-jobs/tasks/bcol_refund_confirmation_task.py b/jobs/payment-jobs/tasks/bcol_refund_confirmation_task.py index 64714a4be..252c7fac8 100644 --- a/jobs/payment-jobs/tasks/bcol_refund_confirmation_task.py +++ b/jobs/payment-jobs/tasks/bcol_refund_confirmation_task.py @@ -13,7 +13,7 @@ # limitations under the License. """Task to update refunded invoices that have been processed by BCOL.""" from __future__ import annotations -from datetime import datetime +from datetime import datetime, timezone from decimal import Decimal from typing import Dict, List @@ -115,6 +115,6 @@ def _compare_and_update_records(cls, invoice_refs: List[InvoiceReference], bcol_ # refund was processed and value is correct. Update invoice state and refund date invoice.invoice_status_code = InvoiceStatus.REFUNDED.value - invoice.refund_date = datetime.now() + invoice.refund_date = datetime.now(tz=timezone.utc) db.session.add(invoice) db.session.commit() diff --git a/jobs/payment-jobs/tasks/cfs_create_account_task.py b/jobs/payment-jobs/tasks/cfs_create_account_task.py index 836c2bda1..775a47705 100644 --- a/jobs/payment-jobs/tasks/cfs_create_account_task.py +++ b/jobs/payment-jobs/tasks/cfs_create_account_task.py @@ -13,7 +13,7 @@ # limitations under the License. """Task to create CFS account offline.""" import re -from datetime import datetime +from datetime import datetime, timezone from typing import Dict from flask import current_app @@ -156,7 +156,7 @@ def _create_cfs_account(cls, pending_account: CfsAccountModel, pay_account: Paym # If the account has an activation time set it should have PENDING_PAD_ACTIVATION status. is_account_in_pad_confirmation_period = pay_account.pad_activation_date is not None and \ - pay_account.pad_activation_date > datetime.today() + pay_account.pad_activation_date > datetime.now(tz=timezone.utc).replace(tzinfo=None) pending_account.status = CfsAccountStatus.PENDING_PAD_ACTIVATION.value if \ is_account_in_pad_confirmation_period else CfsAccountStatus.ACTIVE.value pending_account.save() diff --git a/jobs/payment-jobs/tasks/cfs_create_invoice_task.py b/jobs/payment-jobs/tasks/cfs_create_invoice_task.py index a148079e5..a15aa354a 100644 --- a/jobs/payment-jobs/tasks/cfs_create_invoice_task.py +++ b/jobs/payment-jobs/tasks/cfs_create_invoice_task.py @@ -105,25 +105,20 @@ def _cancel_rs_invoices(cls): for receipt in receipts: CFSService.unapply_receipt(cfs_account, receipt.receipt_number, invoice_reference.invoice_number) - - # Adjust to zero: -invoice.total + invoice.total = 0 - adjustment_negative_amount = -invoice.total - CFSService.adjust_invoice(cfs_account=cfs_account, - inv_number=invoice_reference.invoice_number, - amount=adjustment_negative_amount) + # This used to be adjust invoice, but the suggested way from Tara is to use reverse invoice. + CFSService.reverse_invoice(invoice_reference.invoice_number) except Exception as e: # NOQA # pylint: disable=broad-except capture_message( - f'Error on canelling Routing Slip invoice: invoice id={invoice.id}, ' + f'Error on cancelling Routing Slip invoice: invoice id={invoice.id}, ' f'routing slip : {routing_slip.id}, ERROR : {str(e)}', level='error') current_app.logger.error(e) - # TODO stop execution ? what should be the invoice stats ; should we set it to error or retry? continue invoice_reference.status_code = InvoiceReferenceStatus.CANCELLED.value invoice.invoice_status_code = InvoiceStatus.REFUNDED.value - invoice.refund_date = datetime.now() + invoice.refund_date = datetime.now(tz=timezone.utc) invoice.save() @classmethod @@ -221,7 +216,7 @@ def _create_rs_invoices(cls): # pylint: disable=too-many-locals # leave the status as PAID invoice.invoice_status_code = InvoiceStatus.PAID.value - invoice.payment_date = datetime.now() + invoice.payment_date = datetime.now(tz=timezone.utc) invoice.paid = invoice.total invoice.save() @@ -308,7 +303,7 @@ def _create_pad_invoices(cls): # pylint: disable=too-many-locals additional_params = { 'invoice_total': float(invoice_total), - 'invoice_process_date': f'{datetime.now()}' + 'invoice_process_date': f'{datetime.now(tz=timezone.utc)}' } mailer.publish_mailer_events(QueueMessageTypes.PAD_INVOICE_CREATED.value, payment_account, additional_params) @@ -329,7 +324,7 @@ def _return_eft_accounts(cls): """Return EFT accounts.""" invoice_subquery = db.session.query(InvoiceModel.payment_account_id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.EFT.value) \ - .filter(InvoiceModel.invoice_status_code == InvoiceStatus.CREATED.value).subquery() + .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value).subquery() eft_accounts: List[PaymentAccountModel] = db.session.query(PaymentAccountModel) \ .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \ @@ -341,21 +336,6 @@ def _return_eft_accounts(cls): return eft_accounts - @classmethod - def _save_invoice_reference_records(cls, account_invoices, cfs_account, invoice_response): - """Save invoice reference records.""" - for invoice in account_invoices: - - invoice_reference = EftService.create_invoice_reference( - invoice=invoice, - invoice_number=invoice_response.get('invoice_number'), - reference_number=invoice_response.get('pbc_ref_number', None) - ) - db.session.add(invoice_reference) - - invoice.cfs_account_id = cfs_account.id - db.session.commit() - @classmethod def _active_invoice_reference_subquery(cls): return db.session.query(InvoiceReferenceModel.invoice_id). \ @@ -364,27 +344,19 @@ def _active_invoice_reference_subquery(cls): @classmethod def _create_eft_invoices(cls): """Create EFT invoices in CFS.""" - eft_accounts = cls._return_eft_accounts() - - for eft_account in eft_accounts: - account_invoices = db.session.query(InvoiceModel) \ + # Note we can't roll up for EFT, because doing refunds for invoices it's not possible to get the line + # information back from the API. You need that information when creating an adjustment otherwise revenue + # will flow to the wrong lines. + for eft_account in cls._return_eft_accounts(): + invoices = db.session.query(InvoiceModel) \ .filter(InvoiceModel.payment_account_id == eft_account.id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.EFT.value) \ - .filter(InvoiceModel.invoice_status_code == InvoiceStatus.CREATED.value) \ + .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value) \ .filter(InvoiceModel.id.notin_(cls._active_invoice_reference_subquery())) \ .order_by(InvoiceModel.created_on.desc()).all() - if not account_invoices: - continue - - payment_account: PaymentAccountService = PaymentAccountService.find_by_id(eft_account.id) - - if not payment_account: + if not invoices or not (payment_account := PaymentAccountService.find_by_id(eft_account.id)): continue - - current_app.logger.debug( - f'Found {len(account_invoices)} invoices for account {payment_account.auth_account_id}') - cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(payment_account.id, PaymentMethod.EFT.value) if cfs_account.status not in (CfsAccountStatus.ACTIVE.value, CfsAccountStatus.INACTIVE.value): @@ -392,57 +364,58 @@ def _create_eft_invoices(cls): f'is {payment_account.cfs_account_status} skipping.') continue - lines = [] - invoice_total = Decimal('0') - for invoice in account_invoices: - lines.extend(invoice.payment_line_items) - invoice_total += invoice.total - - invoice_number = account_invoices[-1].id - try: - # Get the first invoice id as the trx number for CFS - invoice_response = CFSService.create_account_invoice(transaction_number=invoice_number, - line_items=lines, - cfs_account=cfs_account) - except Exception as e: # NOQA # pylint: disable=broad-except - # There is a chance that the error is a timeout from CAS side, - # so to make sure we are not missing any data, make a GET call for the invoice we tried to create - # and use it if it got created. - current_app.logger.info(e) # INFO is intentional as sentry alerted only after the following try/catch - has_invoice_created: bool = False + current_app.logger.info( + f'Found {len(invoices)} EFT invoices for account {payment_account.auth_account_id}') + for invoice in invoices: + current_app.logger.debug(f'Creating cfs invoice for invoice {invoice.id}') try: - # add a 10 seconds delay here as safe bet, as CFS takes time to create the invoice - time.sleep(10) - invoice_number = generate_transaction_number(str(invoice_number)) - invoice_response = CFSService.get_invoice( - cfs_account=cfs_account, inv_number=invoice_number - ) - has_invoice_created = invoice_response.get('invoice_number', None) == invoice_number - invoice_total_matches = Decimal(invoice_response.get('total', '0')) == invoice_total - except Exception as exc: # NOQA # pylint: disable=broad-except,unused-variable - # Ignore this error, as it is irrelevant and error on outer level is relevant. - pass - # If no invoice is created raise an error for sentry - if not has_invoice_created: - capture_message(f'Error on creating EFT invoice: account id={payment_account.id}, ' - f'auth account : {payment_account.auth_account_id}, ERROR : {str(e)}', - level='error') - current_app.logger.error(e) - continue - if not invoice_total_matches: - capture_message(f'Error on creating EFT invoice: account id={payment_account.id}, ' - f'auth account : {payment_account.auth_account_id}, Invoice exists: ' - f' CAS total: {invoice_response.get("total", 0)}, PAY-BC total: {invoice_total}', - level='error') - current_app.logger.error(e) - continue - - mailer.publish_mailer_events(QueueMessageTypes.EFT_INVOICE_CREATED.value, payment_account, { - 'invoice_total': float(invoice_total), - 'invoice_process_date': f'{datetime.now(tz=timezone.utc)}' - }) + invoice_response = CFSService.create_account_invoice(transaction_number=invoice.id, + line_items=invoice.payment_line_items, + cfs_account=cfs_account) + except Exception as e: # NOQA # pylint: disable=broad-except + # There is a chance that the error is a timeout from CAS side, + # so to make sure we are not missing any data, make a GET call for the invoice we tried to create + # and use it if it got created. + current_app.logger.info(e) # INFO intentional as sentry alerted only after the following try/catch + has_invoice_created: bool = False + try: + # add a 10 seconds delay here as safe bet, as CFS takes time to create the invoice and + # since this is a job, delay doesn't cause any performance issue + time.sleep(10) + invoice_number = generate_transaction_number(str(invoice.id)) + invoice_response = CFSService.get_invoice( + cfs_account=cfs_account, inv_number=invoice_number + ) + has_invoice_created = invoice_response.get('invoice_number', None) == invoice_number + invoice_total_matches = Decimal(invoice_response.get('total', '0')) == invoice.total + except Exception as exc: # NOQA # pylint: disable=broad-except,unused-variable + # Ignore this error, as it is irrelevant and error on outer level is relevant. + pass + + if not has_invoice_created: + capture_message(f'Error on creating EFT invoice: account id={invoice.payment_account.id}, ' + f'auth account : {invoice.payment_account.auth_account_id}, ERROR : {str(e)}', + level='error') + current_app.logger.error(e) + continue + if not invoice_total_matches: + capture_message(f'Error on creating EFT invoice: account id={payment_account.id}, ' + f'auth account : {payment_account.auth_account_id}, Invoice exists: ' + f' CAS total: {invoice_response.get("total", 0)}, ' + f'PAY-BC total: {invoice.total}', + level='error') + current_app.logger.error(e) + continue - cls._save_invoice_reference_records(account_invoices, cfs_account, invoice_response) + invoice.cfs_account_id = cfs_account.id + # Create ACTIVE invoice reference + invoice_reference = EftService.create_invoice_reference( + invoice=invoice, + invoice_number=invoice_response.get('invoice_number'), + reference_number=invoice_response.get('pbc_ref_number', None) + ) + db.session.add(invoice_reference) + db.session.commit() @classmethod def _create_online_banking_invoices(cls): diff --git a/jobs/payment-jobs/tasks/cfs_payment_method_updater.py b/jobs/payment-jobs/tasks/cfs_payment_method_updater.py deleted file mode 100644 index 8c8cd78fa..000000000 --- a/jobs/payment-jobs/tasks/cfs_payment_method_updater.py +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright © 2019 Province of British Columbia -# -# 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. -"""Update bank name for PAD accounts one time. - -This module is a one time job to update the names. -""" -import os -import ctypes - -import asyncio -from typing import List -import aiohttp - - -from sqlalchemy import event -from sqlalchemy.orm import load_only - -from flask import current_app - -from pay_api.models import db -from pay_api.models.cfs_account import CfsAccount as CfsAccountModel -from pay_api.services.cfs_service import CFSService -from pay_api.utils.enums import CfsAccountStatus, ContentType, PaymentMethod - - -from tasks.cfs_bank_name_updater import create_app -from utils.logger import setup_logging - -setup_logging(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'logging.conf')) # important to do this first - - -def remove_versioning(): - """Remove the versioning, so the number doesn't update when we update rows.""" - # pylint: disable=protected-access - key = [k for k in event.registry._key_to_collection - if k[1] == 'before_flush'][0] - identifier = key[1] - fn = ctypes.cast(key[2], ctypes.py_object).value # get function by id - event.remove(db.session, identifier, fn) - - -def handle_site(task, json): - """Handle the site response.""" - site = json - cfs_payment_method = None - match site.get('receipt_method'): - case 'BCR-PAD Daily' | 'BCR-PAD Stop': - cfs_payment_method = PaymentMethod.PAD.value - case None: - if site.get('customer_profile_class') == 'FAS_CORP_PROFILE': - cfs_payment_method = PaymentMethod.INTERNAL.value - else: - cfs_payment_method = PaymentMethod.ONLINE_BANKING.value - case _: - current_app.logger.error(f'Invalid Payment System: {site.get("receipt_method")}') - cfs_account_number = site.get('account_number') - cfs_party_number = site.get('party_number') - cfs_site_number = site.get('site_number') - cfs_accounts = CfsAccountModel.query.filter(CfsAccountModel.cfs_account == cfs_account_number, - CfsAccountModel.cfs_party == cfs_party_number, - CfsAccountModel.cfs_site == cfs_site_number).all() - for cfs_account in cfs_accounts: - cfs_account.payment_method = cfs_payment_method - cfs_account.flush() - current_app.logger.info(f'Updated CFS Account: {cfs_account_number}' - f' {cfs_party_number} {cfs_site_number} - {task.url} -' - f' {cfs_payment_method}') - - -async def get_sites(cfs_accounts: List[CfsAccountModel]): - """Get sites for the cfs_accounts and update the payment method.""" - current_app.logger.info('Updating CFS account rows with payment method by fetching from CAS. This requires VPN.') - current_app.logger.warning('Make sure ACCOUNT_SECRET_KEY, CFS_CLIENT_ID, CFS_CLIENT_SECRET, CFS_BASE_URL are set.') - current_app.logger.info('Getting access token.') - try: - access_token = CFSService.get_token().json().get('access_token') - except Exception as e: # NOQA pylint:disable=broad-except - current_app.logger.error(f'Error getting access token: {e} - Will need CFS_ACCOUNT.payment_method manually.') - return - - connector = aiohttp.TCPConnector(limit=50) - headers = { - 'Content-Type': ContentType.JSON.value, - 'Authorization': f'Bearer {access_token}' - } - async with aiohttp.ClientSession(connector=connector) as session: - tasks = [] - current_app.logger.info(f'Getting sites for {len(cfs_accounts)} accounts') - for cfs_account in cfs_accounts: - cfs_base: str = current_app.config.get('CFS_BASE_URL') - site_url = f'{cfs_base}/cfs/parties/{cfs_account.cfs_party}' - site_url += f'/accs/{cfs_account.cfs_account}/sites/{cfs_account.cfs_site}/' - current_app.logger.info(f'Getting site: {site_url}') - tasks.append(asyncio.create_task(session.get(site_url, headers=headers, timeout=2000))) - tasks = await asyncio.gather(*tasks, return_exceptions=True) - for task in tasks: - if isinstance(task, aiohttp.ClientConnectionError): - current_app.logger.error(f'{task.url} - Connection error') - elif isinstance(task, asyncio.TimeoutError): - current_app.logger.error(f'{task} - Timeout error') - elif isinstance(task, aiohttp.ClientResponseError): - current_app.logger.error(f'{task.request_info.real_url}- Exception: {task.status}') - elif isinstance(task, Exception): - current_app.logger.error(f'{task.url}- Exception: {task}') - elif task.status != 200: - current_app.logger.error(f'{task.url} - Returned non 200: {task.method} - {task.url} - {task.status}') - else: - handle_site(task, await task.json()) - - cfs_accounts = CfsAccountModel.query.filter(CfsAccountModel.status.in_( - [CfsAccountStatus.PENDING_PAD_ACTIVATION.value])).all() - for cfs_account in cfs_accounts: - cfs_account.payment_method = PaymentMethod.PAD.value - cfs_account.flush() - - db.session.commit() - - -def run_update(): - """Update cfs_account.payment_method.""" - remove_versioning() - cfs_accounts = CfsAccountModel.query.options( - load_only(CfsAccountModel.cfs_account, - CfsAccountModel.cfs_party, - CfsAccountModel.cfs_account, - CfsAccountModel.cfs_site, - CfsAccountModel.payment_method - )).filter(CfsAccountModel.cfs_account.is_not(None), - CfsAccountModel.payment_method.is_(None)).all() - asyncio.run(get_sites(cfs_accounts)) - - -if __name__ == '__main__': - application = create_app() - application.app_context().push() - run_update() diff --git a/jobs/payment-jobs/tasks/common/cgi_ap.py b/jobs/payment-jobs/tasks/common/cgi_ap.py index 8f7311c52..51f5d576e 100644 --- a/jobs/payment-jobs/tasks/common/cgi_ap.py +++ b/jobs/payment-jobs/tasks/common/cgi_ap.py @@ -13,7 +13,7 @@ # limitations under the License. """Base class for CGI AP.""" import os -from datetime import datetime +from datetime import datetime, timezone from flask import current_app from pay_api.utils.enums import EjvFileType @@ -32,14 +32,14 @@ class CgiAP(CgiEjv): def get_batch_header(cls, batch_number, batch_type: str = 'AP'): """Return batch header string.""" return f'{cls._feeder_number()}{batch_type}BH{cls.DELIMITER}{cls._feeder_number()}' \ - f'{get_fiscal_year(datetime.now())}' \ + f'{get_fiscal_year(datetime.now(tz=timezone.utc))}' \ f'{batch_number}{cls._message_version()}{cls.DELIMITER}{os.linesep}' @classmethod def get_batch_trailer(cls, batch_number, batch_total, batch_type: str = 'AP', control_total: int = 0): """Return batch trailer string.""" return f'{cls._feeder_number()}{batch_type}BT{cls.DELIMITER}{cls._feeder_number()}' \ - f'{get_fiscal_year(datetime.now())}{batch_number}' \ + f'{get_fiscal_year(datetime.now(tz=timezone.utc))}{batch_number}' \ f'{control_total:0>15}{cls.format_amount(batch_total)}{cls.DELIMITER}{os.linesep}' @classmethod @@ -48,7 +48,7 @@ def get_ap_header(cls, total, invoice_number, invoice_date): invoice_type = 'ST' remit_code = f"{current_app.config.get('CGI_AP_REMITTANCE_CODE'):<4}" currency = 'CAD' - effective_date = cls._get_date(datetime.now()) + effective_date = cls._get_date(datetime.now(tz=timezone.utc)) invoice_date = cls._get_date(invoice_date) oracle_invoice_batch_name = cls._get_oracle_invoice_batch_name(invoice_number) disbursement_method = 'CHQ' if cls.ap_type == EjvFileType.REFUND else 'EFT' @@ -66,7 +66,7 @@ def get_ap_invoice_line(cls, ap_line: APLine): commit_line_number = f'{cls.EMPTY:<4}' # Pad Zeros to four digits. EG. 0001 line_number = f'{ap_line.line_number:04}' - effective_date = cls._get_date(datetime.now()) + effective_date = cls._get_date(datetime.now(tz=timezone.utc)) line_code = cls._get_line_code(ap_line) ap_line = \ f'{cls._feeder_number()}APIL{cls.DELIMITER}{cls._supplier_number()}{cls._supplier_location()}' \ diff --git a/jobs/payment-jobs/tasks/common/cgi_ejv.py b/jobs/payment-jobs/tasks/common/cgi_ejv.py index aa6b0a58b..0e98c651d 100644 --- a/jobs/payment-jobs/tasks/common/cgi_ejv.py +++ b/jobs/payment-jobs/tasks/common/cgi_ejv.py @@ -15,7 +15,7 @@ import os import tempfile -from datetime import datetime +from datetime import datetime, timezone from flask import current_app from pay_api.models import DistributionCode as DistributionCodeModel @@ -34,7 +34,7 @@ class CgiEjv: @classmethod def get_file_name(cls): """Return file name.""" - date_time = get_nearest_business_day(datetime.now()).strftime('%Y%m%d%H%M%S') + date_time = get_nearest_business_day(datetime.now(tz=timezone.utc)).strftime('%Y%m%d%H%M%S') return f'INBOX.F{cls._feeder_number()}.{date_time}' @classmethod @@ -76,13 +76,13 @@ def get_jv_line(cls, # pylint:disable=too-many-arguments def get_batch_header(cls, batch_number, batch_type): """Return batch header string.""" return f'{cls._feeder_number()}{batch_type}BH{cls.DELIMITER}{cls._feeder_number()}' \ - f'{get_fiscal_year(datetime.now())}' \ + f'{get_fiscal_year(datetime.now(tz=timezone.utc))}' \ f'{batch_number}{cls._message_version()}{cls.DELIMITER}{os.linesep}' @classmethod def get_effective_date(cls): """Return effective date..""" - return get_nearest_business_day(datetime.now()).strftime('%Y%m%d') + return get_nearest_business_day(datetime.now(tz=timezone.utc)).strftime('%Y%m%d') @classmethod def format_amount(cls, amount: float): @@ -126,7 +126,7 @@ def get_jv_header(cls, batch_type, journal_batch_name, journal_name, total): def get_batch_trailer(cls, batch_number, batch_total, batch_type, control_total): """Return batch trailer string.""" return f'{cls._feeder_number()}{batch_type}BT{cls.DELIMITER}{cls._feeder_number()}' \ - f'{get_fiscal_year(datetime.now())}{batch_number}' \ + f'{get_fiscal_year(datetime.now(tz=timezone.utc))}{batch_number}' \ f'{control_total:0>15}{cls.format_amount(batch_total)}{cls.DELIMITER}{os.linesep}' @classmethod diff --git a/jobs/payment-jobs/tasks/common/dataclasses.py b/jobs/payment-jobs/tasks/common/dataclasses.py index 8c03d2fb1..101259d43 100644 --- a/jobs/payment-jobs/tasks/common/dataclasses.py +++ b/jobs/payment-jobs/tasks/common/dataclasses.py @@ -12,15 +12,40 @@ # See the License for the specific language governing permissions and # limitations under the License. """Common dataclasses for tasks, dataclasses allow for cleaner code with autocompletion in vscode.""" +from decimal import Decimal + from dataclasses import dataclass from typing import List, Optional from dataclass_wizard import JSONWizard +from pay_api.models import DistributionCode as DistributionCodeModel from pay_api.models import Invoice as InvoiceModel from pay_api.models import PaymentLineItem as LineItemModel from pay_api.utils.enums import InvoiceStatus from tasks.common.enums import PaymentDetailsGlStatus +@dataclass +class DisbursementLineItem: + """DTO mapping for disbursement line item.""" + + amount: Decimal + flow_through: str + description_identifier: str + is_reversal: bool + is_legacy: bool + disbursement_type: str + identifier: int + + +@dataclass +class Disbursement: + """DTO mapping for disbursement.""" + + bcreg_distribution_code: DistributionCodeModel + partner_distribution_code: DistributionCodeModel + line_item: DisbursementLineItem + + @dataclass class RefundData(JSONWizard): """Refund data from order status query.""" diff --git a/jobs/payment-jobs/tasks/direct_pay_automated_refund_task.py b/jobs/payment-jobs/tasks/direct_pay_automated_refund_task.py index e3976530e..87a84140b 100644 --- a/jobs/payment-jobs/tasks/direct_pay_automated_refund_task.py +++ b/jobs/payment-jobs/tasks/direct_pay_automated_refund_task.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Task to handle Direct Pay automated refunds.""" -from datetime import datetime +from datetime import datetime, timezone from typing import List from flask import current_app @@ -88,7 +88,7 @@ def handle_non_complete_credit_card_refunds(cls): except Exception as e: # NOQA # pylint: disable=broad-except disable=invalid-name capture_message(f'Error on processing credit card refund - invoice: {invoice.id}' f'status={invoice.invoice_status_code} ERROR : {str(e)}', level='error') - current_app.logger.error(e) + current_app.logger.error(e, exc_info=True) @classmethod def _query_order_status(cls, invoice: Invoice): @@ -130,7 +130,7 @@ def _refund_complete(cls, invoice: Invoice): current_app.logger.info( 'Refund complete - GL was posted - setting refund.gl_posted to now.') refund = RefundModel.find_by_invoice_id(invoice.id) - refund.gl_posted = datetime.now() + refund.gl_posted = datetime.now(tz=timezone.utc) refund.save() @staticmethod @@ -166,7 +166,7 @@ def _set_invoice_and_payment_to_refunded(invoice: Invoice): """Set invoice and payment to REFUNDED.""" current_app.logger.info('Invoice & Payment set to REFUNDED, add refund_date.') invoice.invoice_status_code = InvoiceStatus.REFUNDED.value - invoice.refund_date = datetime.now() + invoice.refund_date = datetime.now(tz=timezone.utc) invoice.save() payment = PaymentModel.find_payment_for_invoice(invoice.id) payment.payment_status_code = PaymentStatus.REFUNDED.value diff --git a/jobs/payment-jobs/tasks/distribution_task.py b/jobs/payment-jobs/tasks/distribution_task.py index 416bf2d6a..10ae79b7a 100644 --- a/jobs/payment-jobs/tasks/distribution_task.py +++ b/jobs/payment-jobs/tasks/distribution_task.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Service to manage PAYBC services.""" -from datetime import datetime +from datetime import datetime, timezone from flask import current_app from pay_api.models.distribution_code import DistributionCode as DistributionCodeModel @@ -152,7 +152,7 @@ def update_invoice_to_refunded_or_paid(cls, invoice: InvoiceModel): if invoice.invoice_status_code == InvoiceStatus.UPDATE_REVENUE_ACCOUNT_REFUND.value: # No more work is needed to ensure it was posted to gl. refund = RefundModel.find_by_invoice_id(invoice.id) - refund.gl_posted = datetime.now() + refund.gl_posted = datetime.now(tz=timezone.utc) refund.save() invoice.invoice_status_code = InvoiceStatus.REFUNDED.value else: diff --git a/jobs/payment-jobs/tasks/eft_task.py b/jobs/payment-jobs/tasks/eft_task.py index 8db485a68..f58cb3e78 100644 --- a/jobs/payment-jobs/tasks/eft_task.py +++ b/jobs/payment-jobs/tasks/eft_task.py @@ -14,28 +14,37 @@ """Task for linking electronic funds transfers.""" from datetime import datetime, timezone from typing import List - +from dataclasses import dataclass +from decimal import Decimal from flask import current_app from pay_api.models import CfsAccount as CfsAccountModel -from pay_api.models import EFTCredit as EFTCreditModel from pay_api.models import EFTCreditInvoiceLink as EFTCreditInvoiceLinkModel +from pay_api.models import EFTShortnamesHistorical as EFTShortnameHistoryModel from pay_api.models import Invoice as InvoiceModel from pay_api.models import InvoiceReference as InvoiceReferenceModel +from pay_api.models import Payment as PaymentModel from pay_api.models import Receipt as ReceiptModel from pay_api.models import db from pay_api.services.cfs_service import CFSService +from pay_api.services.eft_service import EftService +from pay_api.services.invoice import Invoice as InvoiceService from pay_api.utils.enums import ( CfsAccountStatus, DisbursementStatus, EFTCreditInvoiceStatus, InvoiceReferenceStatus, InvoiceStatus, PaymentMethod, - PaymentSystem, ReverseOperation) + PaymentStatus, PaymentSystem, ReverseOperation) from sentry_sdk import capture_message from sqlalchemy import func, or_ -from sqlalchemy.orm import lazyload +from sqlalchemy.orm import lazyload, registry + +from utils.auth_event import AuthEvent class EFTTask: # pylint:disable=too-few-public-methods """Task to link electronic funds transfers.""" + history_group_ids = set() + overdue_account_ids = {} + @classmethod def get_eft_credit_invoice_links_by_status(cls, status: str) \ -> List[tuple[InvoiceModel, EFTCreditInvoiceLinkModel, CfsAccountModel]]: @@ -45,107 +54,303 @@ def get_eft_credit_invoice_links_by_status(cls, status: str) \ .filter(CfsAccountModel.status == CfsAccountStatus.ACTIVE.value) \ .group_by(CfsAccountModel.account_id).subquery('latest_cfs_account') - query = db.session.query(InvoiceModel, EFTCreditInvoiceLinkModel, CfsAccountModel) \ - .join(EFTCreditModel, EFTCreditModel.id == EFTCreditInvoiceLinkModel.eft_credit_id) \ - .join(CfsAccountModel, CfsAccountModel.account_id == EFTCreditModel.payment_account_id) \ + cil_rollup = db.session.query(func.min(EFTCreditInvoiceLinkModel.id).label('id'), + EFTCreditInvoiceLinkModel.invoice_id, + EFTCreditInvoiceLinkModel.status_code, + EFTCreditInvoiceLinkModel.receipt_number, + func.array_agg(EFTCreditInvoiceLinkModel.id) # pylint: disable=not-callable + .label('link_ids'), + func.sum(EFTCreditInvoiceLinkModel.amount).label('rollup_amount')) \ + .join(InvoiceReferenceModel, InvoiceReferenceModel.invoice_id == EFTCreditInvoiceLinkModel.invoice_id) \ + .filter(EFTCreditInvoiceLinkModel.status_code == status) \ + .filter(InvoiceReferenceModel.status_code.in_([InvoiceReferenceStatus.ACTIVE.value, + InvoiceReferenceStatus.COMPLETED.value])) \ + .group_by(EFTCreditInvoiceLinkModel.invoice_id, + EFTCreditInvoiceLinkModel.status_code, + EFTCreditInvoiceLinkModel.receipt_number) \ + .subquery() + + # This needs to be local unfortunately so it doesn't get remapped. + @dataclass + class EFTCILRollup: + """Dataclass for rollup so we don't use a tuple instead.""" + + invoice_id: int + status_code: str + receipt_number: str + link_ids: List[int] + rollup_amount: Decimal + + registry().map_imperatively(EFTCILRollup, cil_rollup, primary_key=[cil_rollup.c.invoice_id, + cil_rollup.c.status_code, + cil_rollup.c.receipt_number]) + + query = db.session.query(InvoiceModel, CfsAccountModel, EFTCILRollup) \ + .join(cil_rollup, InvoiceModel.id == cil_rollup.c.invoice_id) \ + .join(CfsAccountModel, CfsAccountModel.account_id == InvoiceModel.payment_account_id) \ .join(latest_cfs_account, CfsAccountModel.id == latest_cfs_account.c.max_id_per_payment_account) \ - .join(InvoiceModel, InvoiceModel.id == EFTCreditInvoiceLinkModel.invoice_id) \ .options(lazyload('*')) \ - .filter(EFTCreditInvoiceLinkModel.status_code == status) + .filter(InvoiceModel.payment_method_code == PaymentMethod.EFT.value) \ + .filter(InvoiceModel.total == cil_rollup.c.rollup_amount) match status: + case EFTCreditInvoiceStatus.CANCELLED.value: + # Handles 3. EFT Credit Link - PENDING, CANCEL that link reverse invoice. See eft_service refund. + query = query.filter( + InvoiceModel.invoice_status_code == InvoiceStatus.REFUND_REQUESTED.value) case EFTCreditInvoiceStatus.PENDING.value: query = query.filter(InvoiceModel.disbursement_status_code.is_(None)) - query = query.filter(InvoiceModel.invoice_status_code.in_([InvoiceStatus.CREATED.value, + query = query.filter(InvoiceModel.invoice_status_code.in_([InvoiceStatus.APPROVED.value, InvoiceStatus.OVERDUE.value])) case EFTCreditInvoiceStatus.PENDING_REFUND.value: - query = query.filter(InvoiceModel.invoice_status_code == InvoiceStatus.PAID.value) + # Handles 4. EFT Credit Link - COMPLETED from refund flow. See eft_service refund. query = query.filter(or_(InvoiceModel.disbursement_status_code.is_( None), InvoiceModel.disbursement_status_code == DisbursementStatus.COMPLETED.value)) + query = query.filter(InvoiceModel.invoice_status_code.in_([InvoiceStatus.PAID.value, + InvoiceStatus.REFUND_REQUESTED.value])) case _: pass - return query.order_by(InvoiceModel.payment_account_id, EFTCreditInvoiceLinkModel.id).all() + return query.order_by(InvoiceModel.payment_account_id, cil_rollup.c.invoice_id).all() @classmethod - def link_electronic_funds_transfers_cfs(cls): + def link_electronic_funds_transfers_cfs(cls) -> dict: """Replicate linked EFT's as receipts inside of CFS and mark invoices as paid.""" credit_invoice_links = cls.get_eft_credit_invoice_links_by_status(EFTCreditInvoiceStatus.PENDING.value) - for invoice, credit_invoice_link, cfs_account in credit_invoice_links: + cls.history_group_ids = set() + for invoice, cfs_account, cil_rollup in credit_invoice_links: try: - current_app.logger.info(f'PayAccount: {invoice.payment_account_id} Id: {credit_invoice_link.id} -' - f' Invoice Id: {invoice.id} - Amount: {credit_invoice_link.amount}') - invoice_reference = InvoiceReferenceModel.find_by_invoice_id_and_status( - credit_invoice_link.invoice_id, InvoiceReferenceStatus.ACTIVE.value - ) - invoice_reference.status_code = InvoiceReferenceStatus.COMPLETED.value - invoice_reference.flush() - # Note: Not creating the entire EFT as a receipt because it can be mapped to multiple CFS accounts. - # eft_credit_invoice_links table should reflect exactly what's in CAS. - receipt_number = f'EFTCIL{credit_invoice_link.id}' - CFSService.create_cfs_receipt( - cfs_account=cfs_account, - rcpt_number=receipt_number, - rcpt_date=credit_invoice_link.created_on.strftime('%Y-%m-%d'), - amount=credit_invoice_link.amount, - payment_method=PaymentMethod.EFT.value, - access_token=CFSService.get_token(PaymentSystem.FAS).json().get('access_token')) - CFSService.apply_receipt(cfs_account, receipt_number, invoice_reference.invoice_number) - ReceiptModel(receipt_number=receipt_number, - receipt_amount=credit_invoice_link.amount, - invoice_id=invoice_reference.invoice_id, - receipt_date=datetime.now(tz=timezone.utc)).flush() - invoice.invoice_status_code = InvoiceStatus.PAID.value - invoice.paid = credit_invoice_link.amount - invoice.payment_date = datetime.now(tz=timezone.utc) - invoice.flush() - # TODO ADD UNLOCK MAILER HERE. - credit_invoice_link.status_code = EFTCreditInvoiceStatus.COMPLETED.value - credit_invoice_link.receipt_number = receipt_number - credit_invoice_link.flush() + current_app.logger.info(f'PayAccount: {invoice.payment_account_id} Id: {cil_rollup.id} -' + f' Invoice Id: {invoice.id} - Amount: {cil_rollup.rollup_amount}') + if invoice.invoice_status_code == InvoiceStatus.OVERDUE.value: + cls.overdue_account_ids[invoice.payment_account_id] = cfs_account.payment_account + receipt_number = f'EFTCIL{cil_rollup.id}' + cls._create_receipt_and_invoice(cfs_account, cil_rollup, invoice, receipt_number) + cls._update_cil_and_shortname_history(cil_rollup, receipt_number=receipt_number) db.session.commit() + EftService().complete_post_invoice(invoice, None) except Exception as e: # NOQA # pylint: disable=broad-except capture_message( f'Error on linking EFT invoice links in CFS ' f'Account id={invoice.payment_account_id} ' - f'EFT Credit invoice Link : {credit_invoice_link.id}' + f'EFT Credit invoice Link : {cil_rollup.id}' f'ERROR : {str(e)}', level='error') current_app.logger.error(f'Error Account id={invoice.payment_account_id} - ' - f'EFT Credit invoice Link : {credit_invoice_link.id}', exc_info=True) + f'EFT Credit invoice Link : {cil_rollup.id}', exc_info=True) db.session.rollback() continue + cls.unlock_overdue_accounts() @classmethod def reverse_electronic_funds_transfers_cfs(cls): """Reverse electronic funds transfers receipts in CFS and reset invoices.""" - credit_invoice_links = cls.get_eft_credit_invoice_links_by_status(EFTCreditInvoiceStatus.PENDING_REFUND.value) - for invoice, credit_invoice_link, cfs_account in credit_invoice_links: + cils = cls.get_eft_credit_invoice_links_by_status(EFTCreditInvoiceStatus.PENDING_REFUND.value) + \ + cls.get_eft_credit_invoice_links_by_status(EFTCreditInvoiceStatus.CANCELLED.value) + cls.history_group_ids = set() + for invoice, cfs_account, cil_rollup in cils: try: - current_app.logger.info(f'PayAccount: {invoice.payment_account_id} Id: {credit_invoice_link.id} -' - f' Invoice Id: {invoice.id} - Amount: {credit_invoice_link.amount}') - receipt_number = credit_invoice_link.receipt_number - invoice_reference = InvoiceReferenceModel.find_by_invoice_id_and_status( - invoice.id, InvoiceReferenceStatus.COMPLETED.value - ) - invoice_reference.status_code = InvoiceReferenceStatus.ACTIVE.value - invoice_reference.flush() - CFSService.reverse_rs_receipt_in_cfs(cfs_account, receipt_number, ReverseOperation.VOID.value) - invoice.invoice_status_code = InvoiceStatus.CREATED.value - invoice.paid = 0 - invoice.refund = credit_invoice_link.amount - invoice.refund_date = datetime.now(tz=timezone.utc) - invoice.flush() - for receipt in ReceiptModel.find_all_receipts_for_invoice(invoice.id): - db.session.delete(receipt) - credit_invoice_link.status_code = EFTCreditInvoiceStatus.REFUNDED.value - credit_invoice_link.flush() + current_app.logger.info(f'PayAccount: {invoice.payment_account_id} Id: {cil_rollup.id} -' + f' Invoice Id: {invoice.id} - Amount: {cil_rollup.rollup_amount}') + receipt_number = cil_rollup.receipt_number + cls._rollback_receipt_and_invoice(cfs_account, invoice, receipt_number, cil_rollup.status_code) + cls._update_cil_and_shortname_history(cil_rollup) db.session.commit() except Exception as e: # NOQA # pylint: disable=broad-except capture_message( f'Error on reversing EFT invoice links in CFS ' f'Account id={invoice.payment_account_id} ' - f'EFT Credit invoice Link : {credit_invoice_link.id}' + f'EFT Credit invoice Link : {cil_rollup.id}' + f'ERROR : {str(e)}', level='error') + current_app.logger.error(f'Error Account id={invoice.payment_account_id} - ' + f'EFT Credit invoice Link : {cil_rollup.id}', exc_info=True) + db.session.rollback() + continue + cls.handle_unlinked_refund_requested_invoices() + + @classmethod + def handle_unlinked_refund_requested_invoices(cls): + """Handle unlinked refund requested invoices.""" + # Handles 2. No EFT Credit Link - Job needs to reverse invoice in CFS from refund flow. See eft_service refund. + invoices = db.session.query(InvoiceModel).outerjoin(EFTCreditInvoiceLinkModel) \ + .filter(InvoiceModel.invoice_status_code == InvoiceStatus.REFUND_REQUESTED.value) \ + .filter(InvoiceModel.payment_method_code == PaymentMethod.EFT.value) \ + .filter(EFTCreditInvoiceLinkModel.id.is_(None)) \ + .all() + + for invoice in invoices: + cfs_account = CfsAccountModel.find_effective_by_payment_method(invoice.payment_account_id, + PaymentMethod.EFT.value) + if not cfs_account: + current_app.logger.error(f'No EFT CFS Account found for pay account id={invoice.payment_account_id}') + continue + invoice_reference = InvoiceReferenceModel.find_by_invoice_id_and_status( + invoice.id, InvoiceReferenceStatus.ACTIVE.value) + try: + cls._handle_invoice_refund(invoice, invoice_reference) + db.session.commit() + except Exception as e: # NOQA # pylint: disable=broad-except + capture_message( + f'Error on reversing unlinked REFUND_REQUESTED EFT invoice in CFS ' + f'Account id={invoice.payment_account_id} ' + f'Invoice id : {invoice.id}' f'ERROR : {str(e)}', level='error') current_app.logger.error(f'Error Account id={invoice.payment_account_id} - ' - f'EFT Credit invoice Link : {credit_invoice_link.id}', exc_info=True) + f'Invoice id : {invoice.id}', exc_info=True) db.session.rollback() continue + + @classmethod + def unlock_overdue_accounts(cls): + """Check and unlock overdue EFT accounts.""" + for (payment_account_id, payment_account) in cls.overdue_account_ids.items(): + if InvoiceService.has_overdue_invoices(payment_account_id): + continue + payment_account.has_overdue_invoices = None + payment_account.save() + AuthEvent.publish_unlock_account_event(payment_account) + + @classmethod + def _get_eft_history_by_group_id(cls, related_group_id: int) -> EFTShortnameHistoryModel: + """Get EFT short name historical record by related group id.""" + return (db.session.query(EFTShortnameHistoryModel) + .filter(EFTShortnameHistoryModel.related_group_link_id == related_group_id)).one_or_none() + + @classmethod + def _finalize_shortname_history(cls, group_set: set, invoice_link: EFTCreditInvoiceLinkModel): + """Finalize EFT short name historical record state.""" + if invoice_link.link_group_id is None or invoice_link.link_group_id in group_set: + return + + group_set.add(invoice_link.link_group_id) + if history_model := cls._get_eft_history_by_group_id(invoice_link.link_group_id): + history_model.hidden = False + history_model.is_processing = False + history_model.flush() + + @classmethod + def _update_cil_and_shortname_history(cls, cil_rollup, receipt_number=None): + """Update electronic invoice links.""" + cils = db.session.query(EFTCreditInvoiceLinkModel).filter( + EFTCreditInvoiceLinkModel.id.in_(cil_rollup.link_ids)).all() + for cil in cils: + if cil.status_code != EFTCreditInvoiceStatus.CANCELLED.value: + cil.status_code = EFTCreditInvoiceStatus.COMPLETED.value if receipt_number \ + else EFTCreditInvoiceStatus.REFUNDED.value + cil.receipt_number = receipt_number or cil.receipt_number + cil.flush() + cls._finalize_shortname_history(cls.history_group_ids, cil) + + @classmethod + def _create_receipt_and_invoice(cls, + cfs_account: CfsAccountModel, + cil_rollup, + invoice: InvoiceModel, + receipt_number: str): + """Create receipt in CFS and marks invoice as paid, with payment and receipt rows.""" + if not (invoice_reference := InvoiceReferenceModel.find_by_invoice_id_and_status( + cil_rollup.invoice_id, InvoiceReferenceStatus.ACTIVE.value + )): + raise LookupError(f'Active Invoice reference not ' + f'found for invoice id: {invoice.id}') + if invoice_reference.is_consolidated: + original_invoice_reference = InvoiceReferenceModel.find_by_invoice_id_and_status( + cil_rollup.invoice_id, InvoiceReferenceStatus.CANCELLED.value, exclude_consolidated=True + ) + if not original_invoice_reference: + raise LookupError(f'Non consolidated cancelled invoice reference not ' + f'found for invoice id: {invoice.id}') + invoice_response = CFSService.get_invoice(cfs_account=cfs_account, + inv_number=original_invoice_reference.invoice_number) + cfs_total = Decimal(invoice_response.get('total', '0')) + invoice_total_matches = cfs_total == invoice.total + if not invoice_total_matches: + raise ValueError(f'SBC-PAY Invoice total {invoice.total} does not match CFS total {cfs_total}') + # Note we do the opposite of this in payment_account. + current_app.logger.info(f'Consolidated invoice found, reversing consolidated ' + f'invoice {invoice_reference.invoice_number}.') + CFSService.reverse_invoice(invoice_reference.invoice_number) + invoice_reference.status_code = InvoiceReferenceStatus.CANCELLED.value + invoice_reference.flush() + invoice_reference = original_invoice_reference + + invoice_reference.status_code = InvoiceReferenceStatus.COMPLETED.value + invoice_reference.flush() + # Note: Not creating the entire EFT as a receipt because it can be mapped to multiple CFS accounts. + # eft_credit_invoice_links table should reflect exactly what's in CAS. + CFSService.create_cfs_receipt( + cfs_account=cfs_account, + rcpt_number=receipt_number, + rcpt_date=datetime.now(tz=timezone.utc).strftime('%Y-%m-%d'), + amount=cil_rollup.rollup_amount, + payment_method=PaymentMethod.EFT.value, + access_token=CFSService.get_token(PaymentSystem.FAS).json().get('access_token')) + CFSService.apply_receipt(cfs_account, receipt_number, invoice_reference.invoice_number) + ReceiptModel(receipt_number=receipt_number, + receipt_amount=cil_rollup.rollup_amount, + invoice_id=invoice_reference.invoice_id, + receipt_date=datetime.now(tz=timezone.utc)).flush() + PaymentModel(payment_method_code=PaymentMethod.EFT.value, + payment_status_code=PaymentStatus.COMPLETED.value, + payment_system_code=PaymentSystem.PAYBC.value, + invoice_number=invoice.id, + invoice_amount=invoice.total, + payment_account_id=cfs_account.account_id, + payment_date=datetime.now(tz=timezone.utc), + paid_amount=cil_rollup.rollup_amount, + receipt_number=receipt_number).flush() + invoice.invoice_status_code = InvoiceStatus.PAID.value + invoice.paid = cil_rollup.rollup_amount + invoice.payment_date = datetime.now(tz=timezone.utc) + invoice.flush() + + @classmethod + def _rollback_receipt_and_invoice(cls, cfs_account: CfsAccountModel, + invoice: InvoiceModel, + receipt_number: str, + cil_status_code: str): + """Rollback receipt in CFS and reset invoice status.""" + invoice_reference_requirement = { + EFTCreditInvoiceStatus.PENDING_REFUND.value: InvoiceReferenceStatus.COMPLETED.value, + EFTCreditInvoiceStatus.CANCELLED.value: InvoiceReferenceStatus.ACTIVE.value + } + invoice_reference_status = invoice_reference_requirement.get(cil_status_code) + invoice_reference = InvoiceReferenceModel.find_by_invoice_id_and_status( + invoice.id, invoice_reference_status + ) + if invoice_reference and invoice_reference.is_consolidated: + raise ValueError(f'Cannot reverse a consolidated invoice {invoice_reference.invoice_number}') + if cil_status_code != EFTCreditInvoiceStatus.CANCELLED.value and not invoice_reference: + raise LookupError(f'{invoice_reference_status} invoice reference ' + f'not found for invoice id: {invoice.id} - {invoice.invoice_status_code}') + is_invoice_refund = invoice.invoice_status_code == InvoiceStatus.REFUND_REQUESTED.value + is_reversal = not is_invoice_refund + CFSService.reverse_rs_receipt_in_cfs(cfs_account, receipt_number, ReverseOperation.VOID.value) + if is_invoice_refund: + cls._handle_invoice_refund(invoice, invoice_reference) + else: + invoice_reference.status_code = InvoiceReferenceStatus.ACTIVE.value + invoice.paid = 0 + invoice.payment_date = None + invoice.invoice_status_code = InvoiceStatus.APPROVED.value + invoice_reference.flush() + invoice.flush() + if is_reversal: + if payment := PaymentModel.find_payment_for_invoice(invoice.id): + db.session.delete(payment) + for receipt in ReceiptModel.find_all_receipts_for_invoice(invoice.id): + db.session.delete(receipt) + + @classmethod + def _handle_invoice_refund(cls, + invoice: InvoiceModel, + invoice_reference: InvoiceReferenceModel): + """Handle invoice refunds adjustment on a non-rolled up invoice.""" + if invoice_reference: + if invoice_reference.is_consolidated: + raise ValueError(f'Cannot reverse a consolidated invoice: {invoice_reference.invoice_number}') + CFSService.reverse_invoice(invoice_reference.invoice_number) + invoice_reference.status_code = InvoiceReferenceStatus.CANCELLED.value + invoice_reference.flush() + invoice.invoice_status_code = InvoiceStatus.REFUNDED.value + invoice.refund_date = datetime.now(tz=timezone.utc) + invoice.refund = invoice.total + invoice.flush() diff --git a/jobs/payment-jobs/tasks/ejv_partner_distribution_task.py b/jobs/payment-jobs/tasks/ejv_partner_distribution_task.py index c32304b01..d800936fd 100644 --- a/jobs/payment-jobs/tasks/ejv_partner_distribution_task.py +++ b/jobs/payment-jobs/tasks/ejv_partner_distribution_task.py @@ -14,7 +14,7 @@ """Task to create Journal Voucher.""" import time -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import List from flask import current_app @@ -26,14 +26,16 @@ from pay_api.models import EjvLink as EjvLinkModel from pay_api.models import FeeSchedule as FeeScheduleModel from pay_api.models import Invoice as InvoiceModel +from pay_api.models import PartnerDisbursements as PartnerDisbursementsModel from pay_api.models import PaymentLineItem as PaymentLineItemModel -from pay_api.models import RefundsPartial as RefundsPartialModel from pay_api.models import Receipt as ReceiptModel from pay_api.models import db from pay_api.utils.enums import DisbursementStatus, EjvFileType, EJVLinkType, InvoiceStatus, PaymentMethod -from sqlalchemy import Date, cast +from sqlalchemy import Date, and_, cast +from decimal import Decimal from tasks.common.cgi_ejv import CgiEjv +from tasks.common.dataclasses import Disbursement, DisbursementLineItem class EjvPartnerDistributionTask(CgiEjv): @@ -44,7 +46,7 @@ def create_ejv_file(cls): """Create JV files and upload to CGI. Steps: - 1. Find all invoices from invoice table for disbursements. + 1. Find all invoices/partial refunds/EFT reversals for disbursements. 2. Group by fee schedule and create JV Header and JV Details. 3. Upload the file to minio for future reference. 4. Upload to sftp for processing. First upload JV file and then a TRG file. @@ -54,257 +56,195 @@ def create_ejv_file(cls): cls._create_ejv_file_for_partner(batch_type='GA') # External ministry @staticmethod - def get_invoices_for_disbursement(partner): - """Return invoices for disbursement. Used by EJV and AP.""" + def get_disbursement_by_distribution_for_partner(partner) -> List[Disbursement]: + """Return disbursements dataclass for partners.""" + # Internal invoices aren't disbursed to partners, DRAWDOWN is handled by the mainframe. + # EFT is handled by the PartnerDisbursements table. + # ##################################################### Original (Legacy way) - invoice.disbursement_status_code + # Eventually we'll abanadon this and use the PartnerDisbursements table for all disbursements. + # We'd need a migration and more changes to move it to the table. + skip_payment_methods = [PaymentMethod.INTERNAL.value, PaymentMethod.DRAWDOWN.value, PaymentMethod.EFT.value] disbursement_date = datetime.today() - timedelta(days=current_app.config.get('DISBURSEMENT_DELAY_IN_DAYS')) - invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \ - .filter(InvoiceModel.invoice_status_code == InvoiceStatus.PAID.value) \ - .filter( - InvoiceModel.payment_method_code.notin_([PaymentMethod.INTERNAL.value, - PaymentMethod.DRAWDOWN.value, - PaymentMethod.EFT.value])) \ - .filter((InvoiceModel.disbursement_status_code.is_(None)) | - (InvoiceModel.disbursement_status_code == DisbursementStatus.ERRORED.value)) \ - .filter(~InvoiceModel.receipts.any(cast(ReceiptModel.receipt_date, Date) >= disbursement_date.date())) \ + base_query = db.session.query(InvoiceModel, PaymentLineItemModel, DistributionCodeModel) \ + .join(PaymentLineItemModel, PaymentLineItemModel.invoice_id == InvoiceModel.id) \ + .join(DistributionCodeModel, + DistributionCodeModel.distribution_code_id == PaymentLineItemModel.fee_distribution_id) \ + .filter(InvoiceModel.payment_method_code.notin_(skip_payment_methods)) \ .filter(InvoiceModel.corp_type_code == partner.code) \ - .all() - current_app.logger.info(invoices) - return invoices + .filter(PaymentLineItemModel.total > 0) \ + .filter(DistributionCodeModel.stop_ejv.is_(False) | DistributionCodeModel.stop_ejv.is_(None)) \ + .order_by(DistributionCodeModel.distribution_code_id, PaymentLineItemModel.id) - @staticmethod - def get_refund_partial_payment_line_items_for_disbursement(partner) -> List[PaymentLineItemModel]: - """Return payment line items with partial refunds for disbursement.""" - payment_line_items: List[PaymentLineItemModel] = db.session.query(PaymentLineItemModel) \ - .join(InvoiceModel, PaymentLineItemModel.invoice_id == InvoiceModel.id) \ - .join(RefundsPartialModel, PaymentLineItemModel.id == RefundsPartialModel.payment_line_item_id) \ + transactions = base_query.filter((InvoiceModel.disbursement_status_code.is_(None)) | + (InvoiceModel.disbursement_status_code == DisbursementStatus.ERRORED.value)) \ + .filter(~InvoiceModel.receipts.any(cast(ReceiptModel.receipt_date, Date) >= disbursement_date.date())) \ .filter(InvoiceModel.invoice_status_code == InvoiceStatus.PAID.value) \ - .filter(InvoiceModel.payment_method_code.in_([PaymentMethod.DIRECT_PAY.value])) \ - .filter((RefundsPartialModel.disbursement_status_code.is_(None)) | - (RefundsPartialModel.disbursement_status_code == DisbursementStatus.ERRORED.value)) \ - .filter(InvoiceModel.corp_type_code == partner.code) \ .all() - current_app.logger.info(payment_line_items) - return payment_line_items - @classmethod - def get_invoices_for_refund_reversal(cls, partner): - """Return invoices for refund reversal.""" # REFUND_REQUESTED for credit card payments, CREDITED for AR and REFUNDED for other payments. - refund_inv_statuses = (InvoiceStatus.REFUNDED.value, InvoiceStatus.REFUND_REQUESTED.value, - InvoiceStatus.CREDITED.value) - - invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \ - .filter(InvoiceModel.invoice_status_code.in_(refund_inv_statuses)) \ - .filter( - InvoiceModel.payment_method_code.notin_([PaymentMethod.INTERNAL.value, - PaymentMethod.DRAWDOWN.value, - PaymentMethod.EFT.value])) \ + reversals = base_query.filter(InvoiceModel.invoice_status_code.in_([InvoiceStatus.REFUNDED.value, + InvoiceStatus.REFUND_REQUESTED.value, + InvoiceStatus.CREDITED.value])) \ .filter(InvoiceModel.disbursement_status_code == DisbursementStatus.COMPLETED.value) \ - .filter(InvoiceModel.corp_type_code == partner.code) \ .all() - current_app.logger.info(invoices) - return invoices + + disbursement_rows = [] + for invoice, payment_line_item, distribution_code in transactions + reversals: + disbursement_rows.append(Disbursement( + bcreg_distribution_code=distribution_code, + partner_distribution_code=distribution_code.disbursement_distribution_code, + line_item=DisbursementLineItem( + amount=payment_line_item.total, + flow_through=f'{invoice.id:<110}', + description_identifier=f'#{invoice.id}', + is_reversal=invoice.invoice_status_code in [InvoiceStatus.REFUNDED.value, + InvoiceStatus.REFUND_REQUESTED.value, + InvoiceStatus.CREDITED.value], + disbursement_type=EJVLinkType.INVOICE.value, + identifier=invoice.id, + is_legacy=True + ) + )) + # ################################################################# END OF Legacy way of handling disbursements. + + # Partner disbursements - New + + # TODO include partial refunds + partner_disbursements = db.session.query(PartnerDisbursementsModel, + PaymentLineItemModel, + DistributionCodeModel) \ + .join(PaymentLineItemModel, and_(PaymentLineItemModel.invoice_id == PartnerDisbursementsModel.target_id, + PartnerDisbursementsModel.target_type == EJVLinkType.INVOICE.value)) \ + .join(DistributionCodeModel, + DistributionCodeModel.distribution_code_id == PaymentLineItemModel.fee_distribution_id) \ + .filter(PartnerDisbursementsModel.status_code.is_(None)) \ + .filter(PartnerDisbursementsModel.partner_code == partner.code) \ + .filter(DistributionCodeModel.stop_ejv.is_(False) | DistributionCodeModel.stop_ejv.is_(None)) \ + .order_by(DistributionCodeModel.distribution_code_id, PaymentLineItemModel.id) \ + .all() + + for partner_disbursement, payment_line_item, distribution_code in partner_disbursements: + suffix = 'PR' if partner_disbursement.disbursement_type == EJVLinkType.PARTIAL_REFUND else '' + flow_through = f'{partner.target_id}-{partner_disbursement.id}{suffix}' + disbursement_rows.append(Disbursement( + bcreg_distribution_code=distribution_code, + partner_distribution_code=distribution_code.partner_distribution_code, + line_item=DisbursementLineItem( + amount=partner_disbursement.amount, + flow_through=flow_through, + description_identifier='#' + flow_through, + is_reversal=partner_disbursement.is_reversal, + disbursement_type=partner_disbursement.disbursement_type, + identifier=partner_disbursement.target_id, + is_legacy=False + ) + )) + disbursement_rows.sort(key=lambda x: x.bcreg_distribution_code.distribution_code_id) + return disbursement_rows @classmethod def _create_ejv_file_for_partner(cls, batch_type: str): # pylint:disable=too-many-locals, too-many-statements """Create EJV file for the partner and upload.""" - ejv_content: str = '' - batch_total: float = 0 - control_total: int = 0 - today = datetime.now() + ejv_content, batch_total, control_total = '', Decimal('0'), Decimal('0') + today = datetime.now(tz=timezone.utc) disbursement_desc = current_app.config.get('CGI_DISBURSEMENT_DESC'). \ format(today.strftime('%B').upper(), f'{today.day:0>2}')[:100] disbursement_desc = f'{disbursement_desc:<100}' - - # Create a ejv file model record. - ejv_file_model: EjvFileModel = EjvFileModel( + ejv_file_model = EjvFileModel( file_type=EjvFileType.DISBURSEMENT.value, file_ref=cls.get_file_name(), disbursement_status_code=DisbursementStatus.UPLOADED.value ).flush() batch_number = cls.get_batch_number(ejv_file_model.id) - - # Get partner list. Each of the partner will go as a JV Header and transactions as JV Details. - partners = cls._get_partners_by_batch_type(batch_type) - current_app.logger.info(partners) - - # JV Batch Header - batch_header: str = cls.get_batch_header(batch_number, batch_type) - - for partner in partners: - # Find all invoices for the partner to disburse. - # This includes invoices which are not PAID and invoices which are refunded and partial refunded. - payment_invoices = cls.get_invoices_for_disbursement(partner) - refund_reversals = cls.get_invoices_for_refund_reversal(partner) - invoices = payment_invoices + refund_reversals - - # Process partial refunds for each partner - refund_partial_items = cls.get_refund_partial_payment_line_items_for_disbursement(partner) - - # If no invoices continue. - if not invoices and not refund_partial_items: + batch_header = cls.get_batch_header(batch_number, batch_type) + effective_date = cls.get_effective_date() + # Each of the partner will go as a JV Header and transactions as JV Details. + for partner in cls._get_partners_by_batch_type(batch_type): + current_app.logger.info(partner) + if not (disbursements := cls.get_disbursement_by_distribution_for_partner(partner)): continue - effective_date: str = cls.get_effective_date() - # Construct journal name - ejv_header_model: EjvFileModel = EjvHeaderModel( + ejv_header_model = EjvHeaderModel( partner_code=partner.code, disbursement_status_code=DisbursementStatus.UPLOADED.value, ejv_file_id=ejv_file_model.id ).flush() - journal_name: str = cls.get_journal_name(ejv_header_model.id) - - # To populate JV Header and JV Details, group these invoices by the distribution - # and create one JV Header and detail for each. - distribution_code_set = set() - invoice_id_list = [] - partial_line_item_id_list = [] - for inv in invoices: - invoice_id_list.append(inv.id) - for line_item in inv.payment_line_items: - distribution_code_set.add(line_item.fee_distribution_id) - - for line_item in refund_partial_items: - partial_line_item_id_list.append(line_item.id) - distribution_code_set.add(line_item.fee_distribution_id) - - for distribution_code_id in list(distribution_code_set): - distribution_code: DistributionCodeModel = DistributionCodeModel.find_by_id(distribution_code_id) - credit_distribution_code: DistributionCodeModel = DistributionCodeModel.find_by_id( - distribution_code.disbursement_distribution_code_id - ) - if credit_distribution_code.stop_ejv: - continue - - line_items = cls._find_line_items_by_invoice_and_distribution( - distribution_code_id, invoice_id_list) - - refund_partial_items = cls._find_refund_partial_items_by_distribution( - distribution_code_id, partial_line_item_id_list) - - total: float = 0 - for line in line_items: - total += line.total - - partial_refund_total: float = 0 - for refund_partial in refund_partial_items: - partial_refund_total += refund_partial.refund_amount - - batch_total += total - batch_total += partial_refund_total - - debit_distribution = cls.get_distribution_string(distribution_code) # Debit from BCREG GL - credit_distribution = cls.get_distribution_string(credit_distribution_code) # Credit to partner GL - - # JV Header + journal_name = cls.get_journal_name(ejv_header_model.id) + sequence = 1 + for disbursement in disbursements: + # debit_distribution and credit_distribution stays as is for invoices which are not PAID ejv_content = '{}{}'.format(ejv_content, # pylint:disable=consider-using-f-string cls.get_jv_header(batch_type, cls.get_journal_batch_name(batch_number), - journal_name, total)) + journal_name, disbursement.line_item.amount)) control_total += 1 - - line_number: int = 0 - for line in line_items: - # JV Details - line_number += 1 - # Flow Through add it as the invoice id. - flow_through = f'{line.invoice_id:<110}' - # debit_distribution and credit_distribution stays as is for invoices which are not PAID - # For reversals, we just need to reverse the debit and credit. - is_reversal = InvoiceModel.find_by_id(line.invoice_id).invoice_status_code in \ - (InvoiceStatus.REFUNDED.value, - InvoiceStatus.REFUND_REQUESTED.value, - InvoiceStatus.CREDITED.value) - - invoice_number = f'#{line.invoice_id}' - description = disbursement_desc[:-len(invoice_number)] + invoice_number - description = f'{description[:100]:<100}' - ejv_content = '{}{}'.format(ejv_content, # pylint:disable=consider-using-f-string - cls.get_jv_line(batch_type, credit_distribution, description, - effective_date, flow_through, journal_name, line.total, - line_number, 'C' if not is_reversal else 'D')) + line_number = 1 + batch_total += disbursement.line_item.amount + dl = disbursement.line_item + description = disbursement_desc[:-len(dl.description_identifier)] + dl.description_identifier + description = f'{description[:100]:<100}' + for credit_debit_row in range(1, 2): + target_distribution = cls.get_distribution_string( + disbursement.partner_distribution_code if credit_debit_row == 1 else disbursement.bcreg_distribution_code + ) + # For payment flow, credit the GL partner code, debit the BCREG GL code. + # Reversal is the opposite debit the GL partner code, credit the BCREG GL Code. + credit_debit = 'D' if dl.is_reversal and credit_debit_row == 1 else \ + 'C' if credit_debit_row == 1 else 'D' + jv_line = cls.get_jv_line(batch_type, + target_distribution, + description, + effective_date, + f'{dl.flow_through:<110}', + journal_name, + dl.amount, + line_number, + credit_debit) + ejv_content = '{}{}'.format(ejv_content, jv_line) # pylint:disable=consider-using-f-string line_number += 1 control_total += 1 - # Add a line here for debit too - ejv_content = '{}{}'.format(ejv_content, # pylint:disable=consider-using-f-string - cls.get_jv_line(batch_type, debit_distribution, description, - effective_date, flow_through, journal_name, line.total, - line_number, 'D' if not is_reversal else 'C')) - - control_total += 1 - - partial_refund_number: int = 0 - for refund_partial in refund_partial_items: - # JV Details for partial refunds - partial_refund_number += 1 - # Flow Through add it as the refunds_partial id. - flow_through = f'{refund_partial.id:<110}' - refund_partial_number = f'#{refund_partial.id}' - description = disbursement_desc[:-len(refund_partial_number)] + refund_partial_number - description = f'{description[:100]:<100}' - - ejv_content = '{}{}'.format(ejv_content, # pylint:disable=consider-using-f-string - cls.get_jv_line(batch_type, credit_distribution, description, - effective_date, flow_through, journal_name, - refund_partial.refund_amount, - partial_refund_number, 'D')) - partial_refund_number += 1 - control_total += 1 - - # Add a line here for debit too - ejv_content = '{}{}'.format(ejv_content, # pylint:disable=consider-using-f-string - cls.get_jv_line(batch_type, debit_distribution, description, - effective_date, flow_through, journal_name, - refund_partial.refund_amount, - partial_refund_number, 'C')) - control_total += 1 + cls._update_disbursement_status_and_ejv_link(dl, ejv_header_model, sequence) + sequence += 1 - # Update partial refund status - refund_partial.disbursement_status_code = DisbursementStatus.UPLOADED.value - - # Create ejv invoice/partial_refund link records and set invoice status - sequence = 1 - sequence = cls._create_ejv_link(invoices, ejv_header_model, sequence, EJVLinkType.INVOICE.value) - cls._create_ejv_link(refund_partial_items, ejv_header_model, sequence, EJVLinkType.REFUND.value) + db.session.flush() if not ejv_content: db.session.rollback() return - # JV Batch Trailer - jv_batch_trailer: str = cls.get_batch_trailer(batch_number, batch_total, batch_type, control_total) - + jv_batch_trailer = cls.get_batch_trailer(batch_number, batch_total, batch_type, control_total) ejv_content = f'{batch_header}{ejv_content}{jv_batch_trailer}' file_path_with_name, trg_file_path, file_name = cls.create_inbox_and_trg_files(ejv_content) cls.upload(ejv_content, file_name, file_path_with_name, trg_file_path) - # commit changes to DB db.session.commit() - # Add a sleep to prevent collision on file name. + # To prevent collision on file name. time.sleep(1) @classmethod - def _find_line_items_by_invoice_and_distribution(cls, distribution_code_id, invoice_id_list) \ - -> List[PaymentLineItemModel]: - """Find and return all payment line items for this distribution.""" - line_items: List[PaymentLineItemModel] = db.session.query(PaymentLineItemModel) \ - .filter(PaymentLineItemModel.invoice_id.in_(invoice_id_list)) \ - .filter(PaymentLineItemModel.total > 0) \ - .filter(PaymentLineItemModel.fee_distribution_id == distribution_code_id) - return line_items - - @classmethod - def _find_refund_partial_items_by_distribution(cls, distribution_code_id, partial_line_item_id_list) \ - -> List[RefundsPartialModel]: - """Find and return all payment line items for this distribution.""" - line_items: List[RefundsPartialModel] = db.session.query(RefundsPartialModel) \ - .join(PaymentLineItemModel, PaymentLineItemModel.id == RefundsPartialModel.payment_line_item_id) \ - .filter(RefundsPartialModel.payment_line_item_id.in_(partial_line_item_id_list)) \ - .filter(RefundsPartialModel.refund_amount > 0) \ - .filter(PaymentLineItemModel.fee_distribution_id == distribution_code_id) \ - .all() - return line_items + def _update_disbursement_status_and_ejv_link(cls, + target: Disbursement, + ejv_header_model: EjvHeaderModel, + sequence: int): + """Update disbursement status and create EJV Link.""" + if target.is_legacy: + invoice = InvoiceModel.find_by_id(target.identifier) + invoice.disbursement_status_code = DisbursementStatus.UPLOADED.value + else: + # Only EFT is using partner disbursements table for now, eventually we want to move our disbursement + # process over to something similar: Where we have an entire table setup that + # is used to track disbursements, instead of just the three column approach that + # doesn't work when there are multiple reversals etc. + partner_disbursement = PartnerDisbursementsModel.find_by_id(target.identifier) + partner_disbursement.status_code = DisbursementStatus.UPLOADED.value + partner_disbursement.processed_on = datetime.now(tz=timezone.utc) + + db.session.add(EjvLinkModel(link_id=target.identifier, + link_type=target.disbursement_type, + ejv_header_id=ejv_header_model.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, + sequence=sequence)) @classmethod def _get_partners_by_batch_type(cls, batch_type) -> List[CorpTypeModel]: @@ -312,45 +252,25 @@ def _get_partners_by_batch_type(cls, batch_type) -> List[CorpTypeModel]: # CREDIT : Ministry GL code -> disbursement_distribution_code_id on distribution_codes table # DEBIT : BC Registry GL Code -> distribution_code on fee_schedule, starts with 112 bc_reg_client_code = current_app.config.get('CGI_BCREG_CLIENT_CODE') # 112 + # Rule for GA. Credit is 112 and debit is 112. + # Rule for GI. Debit is 112 and credit is not 112. query = db.session.query(DistributionCodeModel.distribution_code_id) \ .filter(DistributionCodeModel.stop_ejv.is_(False) | DistributionCodeModel.stop_ejv.is_(None)) \ .filter(DistributionCodeModel.account_id.is_(None)) \ - .filter(DistributionCodeModel.disbursement_distribution_code_id.is_(None)) - - if batch_type == 'GA': - # Rule for GA. Credit is 112 and debit is 112. - partner_distribution_code_ids: List[int] = db.session.scalars(query.filter( - DistributionCodeModel.client == bc_reg_client_code - )).all() - else: - # Rule for GI. Debit is 112 and credit is not 112. - partner_distribution_code_ids: List[int] = db.session.scalars(query.filter( - DistributionCodeModel.client != bc_reg_client_code - )).all() + .filter(DistributionCodeModel.disbursement_distribution_code_id.is_(None)) \ + .filter_boolean(batch_type == 'GA', DistributionCodeModel.client == bc_reg_client_code) \ + .filter_boolean(batch_type == 'GI', DistributionCodeModel.client != bc_reg_client_code) # Find all distribution codes who have these partner distribution codes as disbursement. - fee_query = db.session.query(DistributionCodeModel.distribution_code_id).filter( - DistributionCodeModel.disbursement_distribution_code_id.in_(partner_distribution_code_ids)) - fee_distribution_codes: List[int] = db.session.scalars(fee_query).all() - - corp_type_query = db.session.query(FeeScheduleModel.corp_type_code). \ - join(DistributionCodeLinkModel, - DistributionCodeLinkModel.fee_schedule_id == FeeScheduleModel.fee_schedule_id).\ - filter(DistributionCodeLinkModel.distribution_code_id.in_(fee_distribution_codes)) - corp_type_codes: List[str] = db.session.scalars(corp_type_query).all() - - return db.session.query(CorpTypeModel).filter(CorpTypeModel.code.in_(corp_type_codes)).all() - - @classmethod - def _create_ejv_link(cls, items, ejv_header_model, sequence, link_type): - for item in items: - link_model = EjvLinkModel(link_id=item.id, - link_type=link_type, - ejv_header_id=ejv_header_model.id, - disbursement_status_code=DisbursementStatus.UPLOADED.value, - sequence=sequence) - db.session.add(link_model) - sequence += 1 - item.disbursement_status_code = DisbursementStatus.UPLOADED.value - db.session.flush() - return sequence + partner_distribution_codes = db.session.query(DistributionCodeModel.distribution_code_id).filter( + DistributionCodeModel.disbursement_distribution_code_id.in_(query)) + + corp_type_query = db.session.query(FeeScheduleModel.corp_type_code) \ + .join(DistributionCodeLinkModel, + DistributionCodeLinkModel.fee_schedule_id == FeeScheduleModel.fee_schedule_id) \ + .filter(DistributionCodeLinkModel.distribution_code_id.in_(partner_distribution_codes)) + + result = db.session.query(CorpTypeModel) \ + .filter(CorpTypeModel.has_partner_disbursements.is_(True)) \ + .filter(CorpTypeModel.code.in_(corp_type_query)).all() + return result diff --git a/jobs/payment-jobs/tasks/ejv_payment_task.py b/jobs/payment-jobs/tasks/ejv_payment_task.py index 83b591b4d..ca90bad01 100644 --- a/jobs/payment-jobs/tasks/ejv_payment_task.py +++ b/jobs/payment-jobs/tasks/ejv_payment_task.py @@ -57,7 +57,6 @@ def _create_ejv_file_for_gov_account(cls, batch_type: str): # pylint:disable=to batch_total: float = 0 control_total: int = 0 - # Create a ejv file model record. ejv_file_model: EjvFileModel = EjvFileModel( file_type=EjvFileType.PAYMENT.value, file_ref=cls.get_file_name(), @@ -65,10 +64,8 @@ def _create_ejv_file_for_gov_account(cls, batch_type: str): # pylint:disable=to ).flush() batch_number = cls.get_batch_number(ejv_file_model.id) - # Get all invoices which should be part of the batch type. account_ids = cls._get_account_ids_for_payment(batch_type) - # JV Batch Header batch_header: str = cls.get_batch_header(batch_number, batch_type) current_app.logger.info('Processing accounts.') @@ -77,20 +74,17 @@ def _create_ejv_file_for_gov_account(cls, batch_type: str): # pylint:disable=to # Find all invoices for the gov account to pay. invoices = cls._get_invoices_for_payment(account_id) pay_account: PaymentAccountModel = PaymentAccountModel.find_by_id(account_id) - # If no invoices continue. if not invoices or not pay_account.billable: continue disbursement_desc = f'{pay_account.name[:100]:<100}' effective_date: str = cls.get_effective_date() - # Construct journal name ejv_header_model: EjvFileModel = EjvHeaderModel( payment_account_id=account_id, disbursement_status_code=DisbursementStatus.UPLOADED.value, ejv_file_id=ejv_file_model.id ).flush() journal_name: str = cls.get_journal_name(ejv_header_model.id) - # Distribution code for the account. debit_distribution_code: DistributionCodeModel = DistributionCodeModel.find_by_active_for_account( account_id ) @@ -125,7 +119,7 @@ def _create_ejv_file_for_gov_account(cls, batch_type: str): # pylint:disable=to total += line.total line_distribution = cls.get_distribution_string(line_distribution_code) flow_through = f'{line.invoice_id:<110}' - # Credit to BCREG GL + # Credit to BCREG GL for a transaction (non-reversal) line_number += 1 control_total += 1 # If it's normal payment then the Line distribution goes as Credit, @@ -135,7 +129,7 @@ def _create_ejv_file_for_gov_account(cls, batch_type: str): # pylint:disable=to line.total, line_number, 'C' if not is_jv_reversal else 'D') - # Debit from GOV ACCOUNT GL + # Debit from GOV ACCOUNT GL for a transaction (non-reversal) line_number += 1 control_total += 1 # If it's normal payment then the Gov account GL goes as Debit, @@ -150,7 +144,7 @@ def _create_ejv_file_for_gov_account(cls, batch_type: str): # pylint:disable=to total += line.service_fees service_fee_distribution = cls.get_distribution_string(service_fee_distribution_code) flow_through = f'{line.invoice_id:<110}' - # Credit to BCREG GL + # Credit to BCREG GL for a transaction (non-reversal) line_number += 1 control_total += 1 account_jv = account_jv + cls.get_jv_line(batch_type, service_fee_distribution, @@ -159,7 +153,7 @@ def _create_ejv_file_for_gov_account(cls, batch_type: str): # pylint:disable=to line.service_fees, line_number, 'C' if not is_jv_reversal else 'D') - # Debit from GOV ACCOUNT GL + # Debit from GOV ACCOUNT GL for a transaction (non-reversal) line_number += 1 control_total += 1 account_jv = account_jv + cls.get_jv_line(batch_type, debit_distribution, description, @@ -168,7 +162,6 @@ def _create_ejv_file_for_gov_account(cls, batch_type: str): # pylint:disable=to line_number, 'D' if not is_jv_reversal else 'C') batch_total += total - # Skip if we have no total from the invoices. if total > 0: # A JV header for each account. control_total += 1 @@ -176,7 +169,6 @@ def _create_ejv_file_for_gov_account(cls, batch_type: str): # pylint:disable=to journal_name, total) + account_jv ejv_content = ejv_content + account_jv - # Create ejv invoice link records and set invoice status current_app.logger.info('Creating ejv invoice link records and setting invoice status.') sequence = 1 for inv in invoices: @@ -187,8 +179,6 @@ def _create_ejv_file_for_gov_account(cls, batch_type: str): # pylint:disable=to sequence=sequence) db.session.add(ejv_invoice_link) sequence += 1 - # Set distribution status to invoice - # Create invoice reference record current_app.logger.debug(f'Creating Invoice Reference for invoice id: {inv.id}') inv_ref = InvoiceReferenceModel( invoice_id=inv.id, @@ -203,23 +193,14 @@ def _create_ejv_file_for_gov_account(cls, batch_type: str): # pylint:disable=to db.session.rollback() return - # JV Batch Trailer batch_trailer: str = cls.get_batch_trailer(batch_number, batch_total, batch_type, control_total) - ejv_content = f'{batch_header}{ejv_content}{batch_trailer}' - - # Create a file add this content. file_path_with_name, trg_file_path, file_name = cls.create_inbox_and_trg_files(ejv_content) - - current_app.logger.info('Uploading to ftp.') - - # Upload file and trg to FTP + current_app.logger.info('Uploading to sftp.') cls.upload(ejv_content, file_name, file_path_with_name, trg_file_path) - - # commit changes to DB db.session.commit() - # Add a sleep to prevent collision on file name. + # Sleep to prevent collision on file name. time.sleep(1) @classmethod @@ -227,18 +208,16 @@ def _get_account_ids_for_payment(cls, batch_type) -> List[int]: """Return account IDs for payment.""" # CREDIT : Distribution code against fee schedule # DEBIT : Distribution code against account. + # Rule for GA. Credit is 112 and debit is 112. For BCREG client code is 112 + # Rule for GI. Credit is 112 and debit is not 112. For BCREG client code is 112 bc_reg_client_code = current_app.config.get('CGI_BCREG_CLIENT_CODE') - query = db.session.query(DistributionCodeModel.account_id) \ + account_ids = db.session.query(DistributionCodeModel.account_id) \ .filter(DistributionCodeModel.stop_ejv.is_(False) | DistributionCodeModel.stop_ejv.is_(None)) \ - .filter(DistributionCodeModel.account_id.isnot(None)) - - if batch_type == 'GA': - # Rule for GA. Credit is 112 and debit is 112. For BCREG client code is 112 - account_ids: List[int] = query.filter(DistributionCodeModel.client == bc_reg_client_code) - else: - # Rule for GI. Credit is 112 and debit is not 112. For BCREG client code is 112 - account_ids: List[int] = query.filter(DistributionCodeModel.client != bc_reg_client_code) - return db.session.scalars(account_ids).all() + .filter(DistributionCodeModel.account_id.isnot(None)) \ + .filter_boolean(batch_type == 'GA', DistributionCodeModel.client == bc_reg_client_code) \ + .filter_boolean(batch_type != 'GA', DistributionCodeModel.client != bc_reg_client_code) \ + .all() + return [account_id_tuple[0] for account_id_tuple in account_ids] @classmethod def _get_invoices_for_payment(cls, account_id: int) -> List[InvoiceModel]: diff --git a/jobs/payment-jobs/tasks/routing_slip_task.py b/jobs/payment-jobs/tasks/routing_slip_task.py index 5339fcacc..46b23fd59 100644 --- a/jobs/payment-jobs/tasks/routing_slip_task.py +++ b/jobs/payment-jobs/tasks/routing_slip_task.py @@ -13,7 +13,7 @@ # limitations under the License. """Task to for linking routing slips.""" -from datetime import datetime +from datetime import datetime, timezone from typing import List from flask import current_app @@ -333,7 +333,7 @@ def _create_nsf_invoice(cls, cfs_account: CfsAccountModel, rs_number: str, paid=0, payment_method_code=PaymentMethod.INTERNAL.value, corp_type_code='BCR', - created_on=datetime.now(), + created_on=datetime.now(tz=timezone.utc), created_by='SYSTEM', routing_slip=rs_number ) @@ -403,7 +403,7 @@ def _apply_routing_slips_to_pending_invoices(cls, routing_slip: RoutingSlipModel applied_amount += inv.total inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value inv.invoice_status_code = InvoiceStatus.PAID.value - inv.payment_date = datetime.now() + inv.payment_date = datetime.now(tz=timezone.utc) return applied_amount @@ -442,7 +442,7 @@ def apply_routing_slips_to_invoice(cls, # pylint: disable = too-many-arguments, receipt_amount = receipt_balance_before_apply - float(receipt_response.json().get('unapplied_amount')) receipt.receipt_amount = receipt_amount receipt.invoice_id = invoice.id - receipt.receipt_date = datetime.now() + receipt.receipt_date = datetime.now(tz=timezone.utc) receipt.flush() invoice_from_cfs = CFSService.get_invoice(active_cfs_account, invoice_number) diff --git a/jobs/payment-jobs/tasks/stale_payment_task.py b/jobs/payment-jobs/tasks/stale_payment_task.py index adb974b60..d658232c1 100644 --- a/jobs/payment-jobs/tasks/stale_payment_task.py +++ b/jobs/payment-jobs/tasks/stale_payment_task.py @@ -21,7 +21,11 @@ from pay_api.models import PaymentTransaction as PaymentTransactionModel from pay_api.models import db from pay_api.services import PaymentService, TransactionService -from pay_api.utils.enums import PaymentStatus, TransactionStatus +from pay_api.services.direct_pay_service import DirectPayService +from pay_api.utils.enums import InvoiceReferenceStatus, PaymentStatus, TransactionStatus + + +STATUS_PAID = ('PAID', 'CMPLT') class StalePaymentTask: # pylint: disable=too-few-public-methods @@ -30,9 +34,10 @@ class StalePaymentTask: # pylint: disable=too-few-public-methods @classmethod def update_stale_payments(cls): """Update stale payments.""" - current_app.logger.info(f'StalePaymentTask Ran at {datetime.datetime.now()}') + current_app.logger.info(f'StalePaymentTask Ran at {datetime.datetime.now(tz=datetime.timezone.utc)}') cls._update_stale_payments() cls._delete_marked_payments() + cls._verify_created_direct_pay_invoices() @classmethod def _update_stale_payments(cls): @@ -50,7 +55,8 @@ def _update_stale_payments(cls): .all() if len(stale_transactions) == 0 and len(service_unavailable_transactions) == 0: - current_app.logger.info(f'Stale Transaction Job Ran at {datetime.datetime.now()}.But No records found!') + current_app.logger.info(f'Stale Transaction Job Ran at {datetime.datetime.now(tz=datetime.timezone.utc)}.' + 'But No records found!') for transaction in [*stale_transactions, *service_unavailable_transactions]: try: current_app.logger.info(f'Stale Transaction Job found records.Payment Id: {transaction.payment_id}, ' @@ -78,7 +84,8 @@ def _delete_marked_payments(cls): """ invoices_to_delete = InvoiceModel.find_invoices_marked_for_delete() if len(invoices_to_delete) == 0: - current_app.logger.info(f'Delete Invoice Job Ran at {datetime.datetime.now()}.But No records found!') + current_app.logger.info(f'Delete Invoice Job Ran at {datetime.datetime.now(tz=datetime.timezone.utc)}.' + 'But No records found!') for invoice in invoices_to_delete: try: current_app.logger.info(f'Delete Payment Job found records.Payment Id: {invoice.id}') @@ -87,3 +94,24 @@ def _delete_marked_payments(cls): except BusinessException as err: # just catch and continue .Don't stop current_app.logger.warn('Error on delete_payment') current_app.logger.warn(err) + + @classmethod + def _verify_created_direct_pay_invoices(cls): + """Verify recent invoice with PAYBC.""" + created_invoices = InvoiceModel.find_created_direct_pay_invoices(days=2) + current_app.logger.info(f'Found {len(created_invoices)} Created Invoices to be Verified.') + + for invoice in created_invoices: + try: + current_app.logger.info(f'Verify Invoice Job found records.Invoice Id: {invoice.id}') + paybc_invoice = DirectPayService.query_order_status(invoice, InvoiceReferenceStatus.ACTIVE.value) + + if paybc_invoice.paymentstatus in STATUS_PAID: + current_app.logger.debug('_update_active_transactions') + transaction = TransactionService.find_active_by_invoice_id(invoice.id) + if transaction: + # check existing payment status in PayBC and save receipt + TransactionService.update_transaction(transaction.id, pay_response_url=None) + + except Exception as err: # NOQA # pylint: disable=broad-except + current_app.logger.error(err, exc_info=True) diff --git a/jobs/payment-jobs/tasks/statement_due_task.py b/jobs/payment-jobs/tasks/statement_due_task.py index 9e6970b29..ca69f95d0 100644 --- a/jobs/payment-jobs/tasks/statement_due_task.py +++ b/jobs/payment-jobs/tasks/statement_due_task.py @@ -12,28 +12,37 @@ # See the License for the specific language governing permissions and # limitations under the License. """Task to notify user for any outstanding statement.""" -from datetime import timedelta +from datetime import datetime, timedelta, timezone +from dateutil.relativedelta import relativedelta +import pytz from flask import current_app from pay_api.models import db +from pay_api.models.cfs_account import CfsAccount as CfsAccountModel +from pay_api.models.eft_short_name_links import EFTShortnameLinks as EFTShortnameLinksModel from pay_api.models.invoice import Invoice as InvoiceModel +from pay_api.models.invoice_reference import InvoiceReference as InvoiceReferenceModel +from pay_api.models.non_sufficient_funds import NonSufficientFunds as NonSufficientFundsModel from pay_api.models.payment_account import PaymentAccount as PaymentAccountModel from pay_api.models.statement import Statement as StatementModel from pay_api.models.statement_invoices import StatementInvoices as StatementInvoicesModel from pay_api.models.statement_recipients import StatementRecipients as StatementRecipientsModel -from pay_api.models.statement_settings import StatementSettings as StatementSettingsModel from pay_api.services.flags import flags +from pay_api.services import NonSufficientFundsService from pay_api.services.statement import Statement +from pay_api.services.statement_settings import StatementSettings as StatementSettingsService from pay_api.utils.enums import InvoiceStatus, PaymentMethod, StatementFrequency -from pay_api.utils.util import current_local_time, get_local_time +from pay_api.utils.util import current_local_time from sentry_sdk import capture_message -from sqlalchemy import func from utils.auth_event import AuthEvent from utils.enums import StatementNotificationAction from utils.mailer import StatementNotificationInfo, publish_payment_notification +# IMPORTANT: Due to the nature of dates, run this job at least 08:00 UTC or greater. +# Otherwise it could be triggered the day before due to timeshift for PDT/PST. +# It also needs to run after the statements job. class StatementDueTask: # pylint: disable=too-few-public-methods """Task to notify admin for unpaid statements. @@ -42,64 +51,120 @@ class StatementDueTask: # pylint: disable=too-few-public-methods """ unpaid_status = [InvoiceStatus.SETTLEMENT_SCHEDULED.value, InvoiceStatus.PARTIAL.value, - InvoiceStatus.CREATED.value] + InvoiceStatus.APPROVED.value] + action_date_override = None + auth_account_override = None + statement_date_override = None @classmethod - def process_unpaid_statements(cls): + def process_unpaid_statements(cls, action_date_override=None, + auth_account_override=None, statement_date_override=None): """Notify for unpaid statements with an amount owing.""" eft_enabled = flags.is_on('enable-eft-payment-method', default=False) if eft_enabled: + cls.action_date_override = action_date_override + cls.auth_account_override = auth_account_override + cls.statement_date_override = statement_date_override cls._update_invoice_overdue_status() cls._notify_for_monthly() @classmethod def _update_invoice_overdue_status(cls): """Update the status of any invoices that are overdue.""" - legislative_timezone = current_app.config.get('LEGISLATIVE_TIMEZONE') - overdue_datetime = func.timezone(legislative_timezone, func.timezone('UTC', InvoiceModel.overdue_date)) - - db.session.query(InvoiceModel) \ + # Needs to be non timezone aware. + if cls.action_date_override: + now = datetime.strptime(cls.action_date_override, '%Y-%m-%d').replace(hour=8) + offset_hours = -now.astimezone(pytz.timezone('America/Vancouver')).utcoffset().total_seconds() / 60 / 60 + now = now.replace(hour=int(offset_hours), minute=0, second=0) + else: + now = datetime.now(tz=timezone.utc).replace(tzinfo=None) + query = db.session.query(InvoiceModel) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.EFT.value, InvoiceModel.overdue_date.isnot(None), - func.date(overdue_datetime) <= current_local_time().date(), - InvoiceModel.invoice_status_code.in_(cls.unpaid_status))\ - .update({InvoiceModel.invoice_status_code: InvoiceStatus.OVERDUE.value}, synchronize_session='fetch') - + InvoiceModel.overdue_date <= now, + InvoiceModel.invoice_status_code.in_(cls.unpaid_status)) + if cls.auth_account_override: + current_app.logger.info(f'Using auth account override for auth_account_id: {cls.auth_account_override}') + payment_account_id = db.session.query(PaymentAccountModel.id) \ + .filter(PaymentAccountModel.auth_account_id == cls.auth_account_override) \ + .one() + query = query.filter(InvoiceModel.payment_account_id == payment_account_id[0]) + query.update({InvoiceModel.invoice_status_code: InvoiceStatus.OVERDUE.value}, synchronize_session='fetch') db.session.commit() + @classmethod + def add_to_non_sufficient_funds(cls, payment_account): + """Add the invoice to the non sufficient funds table.""" + invoices = db.session.query(InvoiceModel.id, InvoiceReferenceModel.invoice_number) \ + .join(InvoiceReferenceModel, InvoiceReferenceModel.invoice_id == InvoiceModel.id) \ + .filter(InvoiceModel.payment_account_id == payment_account.id, + InvoiceModel.invoice_status_code == InvoiceStatus.OVERDUE.value, + InvoiceModel.id.notin_( + db.session.query(NonSufficientFundsModel.invoice_id) + )).distinct().all() + cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account.id, PaymentMethod.EFT.value) + for invoice_tuple in invoices: + NonSufficientFundsService.save_non_sufficient_funds(invoice_id=invoice_tuple[0], + invoice_number=invoice_tuple[1], + cfs_account=cfs_account.cfs_account, + description='EFT invoice overdue') + @classmethod def _notify_for_monthly(cls): """Notify for unpaid monthly statements with an amount owing.""" - previous_month = current_local_time().replace(day=1) - timedelta(days=1) - statement_settings = StatementSettingsModel.find_accounts_settings_by_frequency(previous_month, - StatementFrequency.MONTHLY) + previous_month = cls.statement_date_override or current_local_time().replace(day=1) - timedelta(days=1) + statement_settings = StatementSettingsService.find_accounts_settings_by_frequency(previous_month, + StatementFrequency.MONTHLY) eft_payment_accounts = [pay_account for _, pay_account in statement_settings if pay_account.payment_method == PaymentMethod.EFT.value] + if cls.auth_account_override: + current_app.logger.info(f'Using auth account override for auth_account_id: {cls.auth_account_override}') + eft_payment_accounts = [pay_account for pay_account in eft_payment_accounts + if pay_account.auth_account_id == cls.auth_account_override] current_app.logger.info(f'Processing {len(eft_payment_accounts)} EFT accounts for monthly reminders.') for payment_account in eft_payment_accounts: try: - statement = cls._find_most_recent_statement( - payment_account.auth_account_id, StatementFrequency.MONTHLY.value) - action, due_date = cls._determine_action_and_due_date_by_invoice(statement.id) + if not (statement := cls._find_most_recent_statement( + payment_account.auth_account_id, StatementFrequency.MONTHLY.value)): + continue + action, due_date = cls._determine_action_and_due_date_by_invoice(statement) total_due = Statement.get_summary(payment_account.auth_account_id, statement.id)['total_due'] - if action and total_due > 0 and (emails := cls._determine_recipient_emails(statement, action)): + if action and total_due > 0: if action == StatementNotificationAction.OVERDUE: - current_app.logger.info('Freezing payment account id: %s and locking auth account id: %s', + current_app.logger.info('Freezing payment account id: %s locking auth account id: %s', payment_account.id, payment_account.auth_account_id) - AuthEvent.publish_lock_account_event(payment_account) - publish_payment_notification( - StatementNotificationInfo(auth_account_id=payment_account.auth_account_id, - statement=statement, - action=action, - due_date=due_date, - emails=emails, - total_amount_owing=total_due)) + # The locking email is sufficient for overdue, no seperate email required. + additional_emails = current_app.config.get('EFT_OVERDUE_NOTIFY_EMAILS') + AuthEvent.publish_lock_account_event(payment_account, additional_emails) + statement.overdue_notification_date = datetime.now(tz=timezone.utc) + # Saving here because the code below can exception, we don't want to send the lock email twice. + statement.save() + payment_account.has_overdue_invoices = datetime.now(tz=timezone.utc) + payment_account.save() + cls.add_to_non_sufficient_funds(payment_account) + continue + if emails := cls._determine_recipient_emails(statement): + current_app.logger.info(f'Sending statement {statement.id} {action}' + f' notification for auth_account_id=' + f'{payment_account.auth_account_id}, payment_account_id=' + f'{payment_account.id}') + links_count = EFTShortnameLinksModel.get_short_name_links_count(payment_account.auth_account_id) + publish_payment_notification( + StatementNotificationInfo(auth_account_id=payment_account.auth_account_id, + statement=statement, + action=action, + due_date=due_date, + emails=emails, + total_amount_owing=total_due, + short_name_links_count=links_count)) except Exception as e: # NOQA # pylint: disable=broad-except capture_message( f'Error on unpaid statement notification auth_account_id={payment_account.auth_account_id}, ' f'ERROR : {str(e)}', level='error') - current_app.logger.error(e) + current_app.logger.error( + f'Error on unpaid statement notification auth_account_id={payment_account.auth_account_id}', + exc_info=True) continue @classmethod @@ -111,14 +176,15 @@ def _find_most_recent_statement(cls, auth_account_id: str, statement_frequency: .filter(StatementModel.frequency == statement_frequency) \ .order_by(StatementModel.to_date.desc()) - return query.first() + statement = query.first() + return statement if statement and statement.overdue_notification_date is None else None @classmethod - def _determine_action_and_due_date_by_invoice(cls, statement_id: int): + def _determine_action_and_due_date_by_invoice(cls, statement: StatementModel): """Find the most overdue invoice for a statement and provide an action.""" invoice = db.session.query(InvoiceModel) \ .join(StatementInvoicesModel, StatementInvoicesModel.invoice_id == InvoiceModel.id) \ - .filter(StatementInvoicesModel.statement_id == statement_id) \ + .filter(StatementInvoicesModel.statement_id == statement.id) \ .filter(InvoiceModel.overdue_date.isnot(None)) \ .order_by(InvoiceModel.overdue_date.asc()) \ .first() @@ -126,27 +192,33 @@ def _determine_action_and_due_date_by_invoice(cls, statement_id: int): if invoice is None: return None, None - day_before_invoice_overdue = get_local_time(invoice.overdue_date).date() - timedelta(days=1) - seven_days_before_invoice_due = day_before_invoice_overdue - timedelta(days=7) - now_date = current_local_time().date() + # 1. EFT Invoice created between or on January 1st <-> January 31st + # 2. Statement Day February 1st + # 3. 7 day reminder Feb 21th (due date - 7) + # 4. Final reminder Feb 28th (due date client should be told to pay by this time) + # 5. Overdue Date and account locked March 15th + day_invoice_due = statement.to_date + relativedelta(months=1) + seven_days_before_invoice_due = day_invoice_due - timedelta(days=7) + + # Needs to be non timezone aware for comparison. + if cls.action_date_override: + now_date = datetime.strptime(cls.action_date_override, '%Y-%m-%d').date() + else: + now_date = datetime.now(tz=timezone.utc).replace(tzinfo=None).date() if invoice.invoice_status_code == InvoiceStatus.OVERDUE.value: - return StatementNotificationAction.OVERDUE, day_before_invoice_overdue - if day_before_invoice_overdue == now_date: - return StatementNotificationAction.DUE, day_before_invoice_overdue + return StatementNotificationAction.OVERDUE, day_invoice_due + if day_invoice_due == now_date: + return StatementNotificationAction.DUE, day_invoice_due if seven_days_before_invoice_due == now_date: - return StatementNotificationAction.REMINDER, day_before_invoice_overdue - return None, day_before_invoice_overdue + return StatementNotificationAction.REMINDER, day_invoice_due + return None, day_invoice_due @classmethod - def _determine_recipient_emails(cls, - statement: StatementRecipientsModel, action: StatementNotificationAction) -> str: + def _determine_recipient_emails(cls, statement: StatementRecipientsModel) -> str: if (recipients := StatementRecipientsModel.find_all_recipients_for_payment_id(statement.payment_account_id)): recipients = ','.join([str(recipient.email) for recipient in recipients]) - if action == StatementNotificationAction.OVERDUE: - if overdue_notify_emails := current_app.config.get('EFT_OVERDUE_NOTIFY_EMAILS'): - recipients += ',' + overdue_notify_emails return recipients - current_app.logger.info(f'No recipients found for statement: {statement.payment_account_id}. Skipping sending.') + current_app.logger.error(f'No recipients found for payment_account_id: {statement.payment_account_id}. Skip.') return None diff --git a/jobs/payment-jobs/tasks/statement_notification_task.py b/jobs/payment-jobs/tasks/statement_notification_task.py index 927bd368c..ead5242cc 100644 --- a/jobs/payment-jobs/tasks/statement_notification_task.py +++ b/jobs/payment-jobs/tasks/statement_notification_task.py @@ -13,7 +13,7 @@ # limitations under the License. """Service to manage PAYBC services.""" import json -from datetime import datetime +from datetime import datetime, timezone from flask import current_app from jinja2 import Environment, FileSystemLoader @@ -62,7 +62,7 @@ def send_notifications(cls): template = ENV.get_template('statement_notification.html') for statement in statements_with_pending_notifications: statement.notification_status_code = NotificationStatus.PROCESSING.value - statement.notification_date = datetime.now() + statement.notification_date = datetime.now(tz=timezone.utc) statement.commit() payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(statement.payment_account_id) recipients = StatementRecipientsModel.find_all_recipients_for_payment_id(statement.payment_account_id) @@ -70,7 +70,7 @@ def send_notifications(cls): current_app.logger.info(f'No recipients found for statement: ' f'{statement.payment_account_id}.Skipping sending') statement.notification_status_code = NotificationStatus.SKIP.value - statement.notification_date = datetime.now() + statement.notification_date = datetime.now(tz=timezone.utc) statement.commit() continue @@ -92,7 +92,7 @@ def send_notifications(cls): result['total_due'], to_emails) else: # EFT not enabled - mark skip - shouldn't happen, but safeguard for manual data injection statement.notification_status_code = NotificationStatus.SKIP.value - statement.notification_date = datetime.now() + statement.notification_date = datetime.now(tz=timezone.utc) statement.commit() continue except Exception as e: # NOQA # pylint:disable=broad-except @@ -103,11 +103,11 @@ def send_notifications(cls): if not notification_success: current_app.logger.error(' 0 else None auth_account_override = arguments[1] if arguments and len(arguments) > 1 else None - target_time = get_local_time(datetime.now()) if date_override is None \ + target_time = get_local_time(datetime.now(tz=timezone.utc)) if date_override is None \ else datetime.strptime(date_override, '%Y-%m-%d') + timedelta(days=1) cls.has_date_override = date_override is not None cls.has_account_override = auth_account_override is not None @@ -60,6 +64,8 @@ def generate_statements(cls, arguments=None): generate_monthly = target_time.day == 1 cls._generate_daily_statements(target_time, auth_account_override) + if not generate_weekly: + cls._generate_gap_statements(target_time, auth_account_override) if generate_weekly: cls._generate_weekly_statements(target_time, auth_account_override) if generate_monthly: @@ -68,12 +74,35 @@ def generate_statements(cls, arguments=None): # Commit transaction db.session.commit() + @classmethod + def _generate_gap_statements(cls, target_time, account_override): + """Generate gap statements for weekly statements that wont run over Sunday.""" + # Look at the target_time versus the end date. + previous_day = get_previous_day(target_time) + statement_from, _ = get_week_start_and_end_date(previous_day, index=0) + statement_from = statement_from.date() + statement_to = previous_day.date() + statement_settings = StatementSettingsService.find_accounts_settings_by_frequency(previous_day, + StatementFrequency.WEEKLY, + from_date=statement_from, + to_date=statement_to) + if statement_from == statement_to or not statement_settings: + return + current_app.logger.debug(f'Found {len(statement_settings)} accounts to generate GAP statements') + search_filter = { + 'dateFilter': { + 'startDate': statement_from.strftime('%Y-%m-%d'), + 'endDate': statement_to.strftime('%Y-%m-%d') + } + } + cls._create_statement_records(search_filter, statement_settings, account_override) + @classmethod def _generate_daily_statements(cls, target_time: datetime, account_override: str): """Generate daily statements for all accounts with settings to generate daily.""" previous_day = get_previous_day(target_time) - statement_settings = StatementSettingsModel.find_accounts_settings_by_frequency(previous_day, - StatementFrequency.DAILY) + statement_settings = StatementSettingsService.find_accounts_settings_by_frequency(previous_day, + StatementFrequency.DAILY) current_app.logger.debug(f'Found {len(statement_settings)} accounts to generate DAILY statements') search_filter = { 'dateFilter': { @@ -87,8 +116,8 @@ def _generate_daily_statements(cls, target_time: datetime, account_override: str def _generate_weekly_statements(cls, target_time: datetime, account_override: str): """Generate weekly statements for all accounts with settings to generate weekly.""" previous_day = get_previous_day(target_time) - statement_settings = StatementSettingsModel.find_accounts_settings_by_frequency(previous_day, - StatementFrequency.WEEKLY) + statement_settings = StatementSettingsService.find_accounts_settings_by_frequency(previous_day, + StatementFrequency.WEEKLY) current_app.logger.debug(f'Found {len(statement_settings)} accounts to generate WEEKLY statements') statement_from, statement_to = get_week_start_and_end_date(previous_day, index=1) search_filter = { @@ -104,8 +133,8 @@ def _generate_weekly_statements(cls, target_time: datetime, account_override: st def _generate_monthly_statements(cls, target_time: datetime, account_override: str): """Generate monthly statements for all accounts with settings to generate monthly.""" previous_day = get_previous_day(target_time) - statement_settings = StatementSettingsModel.find_accounts_settings_by_frequency(previous_day, - StatementFrequency.MONTHLY) + statement_settings = StatementSettingsService.find_accounts_settings_by_frequency(previous_day, + StatementFrequency.MONTHLY) current_app.logger.debug(f'Found {len(statement_settings)} accounts to generate MONTHLY statements') last_month, last_month_year = get_previous_month_and_year(target_time) search_filter = { @@ -117,21 +146,60 @@ def _generate_monthly_statements(cls, target_time: datetime, account_override: s cls._create_statement_records(search_filter, statement_settings, account_override) + @classmethod + def _upsert_statements(cls, statement_settings, invoice_detail_tuple, reuse_statements): + """Upsert statements to reuse statement ids because they are referenced in the EFT Shortname History.""" + statements = [] + for setting, pay_account in statement_settings: + existing_statement = next( + (statement for statement in reuse_statements + if statement.payment_account_id == pay_account.id and + statement.frequency == setting.frequency and + statement.from_date == cls.statement_from.date() and statement.to_date == cls.statement_to.date()), + None + ) + notification_status = NotificationStatus.PENDING.value \ + if pay_account.statement_notification_enabled is True and cls.has_date_override is False \ + else NotificationStatus.SKIP.value + payment_methods = StatementService.determine_payment_methods(invoice_detail_tuple, + pay_account, + existing_statement) + created_on = get_local_time(datetime.now(tz=timezone.utc)) + if existing_statement: + current_app.logger.debug(f'Reusing existing statement already exists for {cls.statement_from.date()}') + existing_statement.notification_status_code = notification_status + existing_statement.payment_methods = payment_methods + existing_statement.created_on = created_on + statements.append(existing_statement) + else: + statements.append(StatementModel( + frequency=setting.frequency, + statement_settings_id=setting.id, + payment_account_id=pay_account.id, + created_on=created_on, + from_date=cls.statement_from, + to_date=cls.statement_to, + notification_status_code=notification_status, + payment_methods=payment_methods + )) + return statements + @classmethod def _create_statement_records(cls, search_filter, statement_settings, account_override: str): - statement_from = None - statement_to = None + cls.statement_from = None + cls.statement_to = None if search_filter.get('dateFilter', None): - statement_from = parse(search_filter.get('dateFilter').get('startDate')) - statement_to = parse(search_filter.get('dateFilter').get('endDate')) - if statement_from == statement_to: - current_app.logger.debug(f'Statements for day: {statement_from.date()}') + cls.statement_from = parse(search_filter.get('dateFilter').get('startDate')) + cls.statement_to = parse(search_filter.get('dateFilter').get('endDate')) + if cls.statement_from == cls.statement_to: + current_app.logger.debug(f'Statements for day: {cls.statement_from.date()}') else: - current_app.logger.debug(f'Statements for week: {statement_from.date()} to {statement_to.date()}') + current_app.logger.debug(f'Statements for week: {cls.statement_from.date()} to ' + f'{cls.statement_to.date()}') elif search_filter.get('monthFilter', None): - statement_from, statement_to = get_first_and_last_dates_of_month( + cls.statement_from, cls.statement_to = get_first_and_last_dates_of_month( search_filter.get('monthFilter').get('month'), search_filter.get('monthFilter').get('year')) - current_app.logger.debug(f'Statements for month: {statement_from.date()} to {statement_to.date()}') + current_app.logger.debug(f'Statements for month: {cls.statement_from.date()} to {cls.statement_to.date()}') if cls.has_account_override: auth_account_ids = [account_override] statement_settings = cls._filter_settings_by_override(statement_settings, account_override) @@ -142,22 +210,13 @@ def _create_statement_records(cls, search_filter, statement_settings, account_ov # Force match on these methods where if the payment method is in matchPaymentMethods, the invoice payment method # must match the account payment method. Used for EFT so the statements only show EFT invoices and interim # statement logic when transitioning payment methods - search_filter['matchPaymentMethods'] = [PaymentMethod.EFT.value] - invoices_and_auth_ids = PaymentModel.get_invoices_for_statements(search_filter) + search_filter['matchPaymentMethods'] = True + invoice_detail_tuple = PaymentModel.get_invoices_and_payment_accounts_for_statements(search_filter) + reuse_statements = [] if cls.has_date_override and statement_settings: - cls._clean_up_old_statements(statement_settings, statement_from, statement_to) - current_app.logger.debug('Inserting statements.') - statements = [StatementModel( - frequency=setting.frequency, - statement_settings_id=setting.id, - payment_account_id=pay_account.id, - created_on=get_local_time(datetime.now()), - from_date=statement_from, - to_date=statement_to, - notification_status_code=NotificationStatus.PENDING.value - if pay_account.statement_notification_enabled is True and cls.has_date_override is False - else NotificationStatus.SKIP.value - ) for setting, pay_account in statement_settings] + reuse_statements = cls._clean_up_old_statements(statement_settings) + current_app.logger.debug('Upserting statements.') + statements = cls._upsert_statements(statement_settings, invoice_detail_tuple, reuse_statements) # Return defaults which returns the id. db.session.bulk_save_objects(statements, return_defaults=True) db.session.flush() @@ -165,7 +224,7 @@ def _create_statement_records(cls, search_filter, statement_settings, account_ov current_app.logger.debug('Inserting statement invoices.') statement_invoices = [] for statement, auth_account_id in zip(statements, auth_account_ids): - invoices = [i for i in invoices_and_auth_ids if i.auth_account_id == auth_account_id] + invoices = [i for i in invoice_detail_tuple if i.auth_account_id == auth_account_id] statement_invoices = statement_invoices + [StatementInvoicesModel( statement_id=statement.id, invoice_id=invoice.id @@ -173,27 +232,29 @@ def _create_statement_records(cls, search_filter, statement_settings, account_ov db.session.bulk_save_objects(statement_invoices) @classmethod - def _clean_up_old_statements(cls, statement_settings, statement_from, statement_to): + def _clean_up_old_statements(cls, statement_settings): """Clean up duplicate / old statements before generating.""" payment_account_ids = [pay_account.id for _, pay_account in statement_settings] - remove_statements = db.session.query(StatementModel)\ + payment_account_ids = select(func.unnest(cast(payment_account_ids, ARRAY(INTEGER)))) + existing_statements = db.session.query(StatementModel)\ .filter_by( frequency=statement_settings[0].StatementSettings.frequency, - from_date=statement_from.date(), to_date=statement_to.date())\ + from_date=cls.statement_from.date(), to_date=cls.statement_to.date(), + is_interim_statement=False)\ .filter(StatementModel.payment_account_id.in_(payment_account_ids))\ .all() - current_app.logger.debug(f'Removing {len(remove_statements)} existing duplicate/stale statements.') - remove_statements_ids = [statement.id for statement in remove_statements] + current_app.logger.debug(f'Removing {len(existing_statements)} existing duplicate/stale statement invoices.') + remove_statements_ids = [statement.id for statement in existing_statements] remove_statement_invoices = db.session.query(StatementInvoicesModel)\ - .filter(StatementInvoicesModel.statement_id.in_(remove_statements_ids))\ + .filter(StatementInvoicesModel.statement_id.in_( + select(func.unnest(cast(remove_statements_ids, ARRAY(INTEGER))))))\ .all() statement_invoice_ids = [statement_invoice.id for statement_invoice in remove_statement_invoices] delete_statement_invoice = delete(StatementInvoicesModel)\ - .where(StatementInvoicesModel.id.in_(statement_invoice_ids)) + .where(StatementInvoicesModel.id.in_(select(func.unnest(cast(statement_invoice_ids, ARRAY(INTEGER)))))) db.session.execute(delete_statement_invoice) db.session.flush() - delete_statement = delete(StatementModel).where(StatementModel.id.in_(remove_statements_ids)) - db.session.execute(delete_statement) + return existing_statements @classmethod def _filter_settings_by_override(cls, statement_settings, auth_account_id: str): diff --git a/jobs/payment-jobs/tasks/unpaid_invoice_notify_task.py b/jobs/payment-jobs/tasks/unpaid_invoice_notify_task.py index 8aa7cba21..8da43550b 100644 --- a/jobs/payment-jobs/tasks/unpaid_invoice_notify_task.py +++ b/jobs/payment-jobs/tasks/unpaid_invoice_notify_task.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Task to notify user for any outstanding invoice for online banking.""" -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from flask import current_app from pay_api.models import CfsAccount as CfsAccountModel @@ -50,7 +50,7 @@ def _notify_for_ob(cls): # pylint: disable=too-many-locals """ unpaid_status = ( InvoiceStatus.SETTLEMENT_SCHEDULED.value, InvoiceStatus.PARTIAL.value, InvoiceStatus.CREATED.value) - notification_date = datetime.today() - timedelta(days=current_app.config.get('NOTIFY_AFTER_DAYS')) + notification_date = datetime.now(tz=timezone.utc) - timedelta(days=current_app.config.get('NOTIFY_AFTER_DAYS')) # Get distinct accounts with pending invoices for that exact day notification_pending_accounts = db.session.query(InvoiceModel.payment_account_id).distinct().filter(and_( InvoiceModel.invoice_status_code.in_(unpaid_status), diff --git a/jobs/payment-jobs/tests/jobs/conftest.py b/jobs/payment-jobs/tests/jobs/conftest.py index 832ed837f..0ace35a73 100644 --- a/jobs/payment-jobs/tests/jobs/conftest.py +++ b/jobs/payment-jobs/tests/jobs/conftest.py @@ -150,3 +150,33 @@ def docker_compose_files(pytestconfig): return [ os.path.join(str(pytestconfig.rootdir), 'tests/docker', 'docker-compose.yml') ] + + +@pytest.fixture() +def admin_users_mock(monkeypatch): + """Mock auth rest call to get org admins.""" + def get_account_admin_users(payment_account): + return { + 'members': [ + { + 'id': 4048, + 'membershipStatus': 'ACTIVE', + 'membershipTypeCode': 'ADMIN', + 'user': { + 'contacts': [ + { + 'email': 'test@test.com', + 'phone': '(250) 111-2222', + 'phoneExtension': '' + } + ], + 'firstname': 'FIRST', + 'id': 18, + 'lastname': 'LAST', + 'loginSource': 'BCSC' + } + } + ] + } + monkeypatch.setattr('pay_api.services.auth.get_account_admin_users', + get_account_admin_users) diff --git a/jobs/payment-jobs/tests/jobs/factory.py b/jobs/payment-jobs/tests/jobs/factory.py index ee0c4e888..bcb553952 100644 --- a/jobs/payment-jobs/tests/jobs/factory.py +++ b/jobs/payment-jobs/tests/jobs/factory.py @@ -17,15 +17,17 @@ Test-Suite to ensure that the /payments endpoint is working as expected. """ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone +from random import randrange from pay_api.models import ( CfsAccount, DistributionCode, DistributionCodeLink, EFTCredit, EFTCreditInvoiceLink, EFTFile, EFTShortnameLinks, - EFTShortnames, EFTTransaction, Invoice, InvoiceReference, Payment, PaymentAccount, PaymentLineItem, Receipt, Refund, - RefundsPartial, RoutingSlip, StatementRecipients, StatementSettings) + EFTShortnames, EFTShortnamesHistorical, EFTTransaction, Invoice, InvoiceReference, Payment, PaymentAccount, + PaymentLineItem, Receipt, Refund, RefundsPartial, RoutingSlip, Statement, StatementInvoices, StatementRecipients, + StatementSettings) from pay_api.utils.enums import ( - CfsAccountStatus, EFTProcessStatus, EFTShortnameStatus, InvoiceReferenceStatus, InvoiceStatus, LineItemStatus, - PaymentMethod, PaymentStatus, PaymentSystem, RoutingSlipStatus) + CfsAccountStatus, EFTHistoricalTypes, EFTProcessStatus, EFTShortnameStatus, InvoiceReferenceStatus, InvoiceStatus, + LineItemStatus, PaymentMethod, PaymentStatus, PaymentSystem, RoutingSlipStatus) def factory_premium_payment_account(bcol_user_id='PB25020', bcol_account_id='1234567890', auth_account_id='1234'): @@ -50,7 +52,33 @@ def factory_statement_recipient(auth_user_id: int, first_name: str, last_name: s ).save() -def factory_statement_settings(pay_account_id: str, frequency='DAILY', from_date=datetime.now(), +def factory_statement_invoices( + statement_id: str, + invoice_id: str): + """Return Factory.""" + return StatementInvoices(statement_id=statement_id, + invoice_id=invoice_id).save() + + +def factory_statement( + frequency: str = 'WEEKLY', + payment_account_id: str = None, + from_date: datetime = datetime.now(tz=timezone.utc), + to_date: datetime = datetime.now(tz=timezone.utc), + statement_settings_id: str = None, + created_on: datetime = datetime.now(tz=timezone.utc), + payment_methods: str = PaymentMethod.EFT.value): + """Return Factory.""" + return Statement(frequency=frequency, + statement_settings_id=statement_settings_id, + payment_account_id=payment_account_id, + from_date=from_date, + to_date=to_date, + created_on=created_on, + payment_methods=payment_methods).save() + + +def factory_statement_settings(pay_account_id: str, frequency='DAILY', from_date=datetime.now(tz=timezone.utc), to_date=None) -> StatementSettings: """Return Factory.""" return StatementSettings( @@ -64,7 +92,7 @@ def factory_statement_settings(pay_account_id: str, frequency='DAILY', from_date def factory_payment( payment_system_code: str = 'PAYBC', payment_method_code: str = 'CC', payment_status_code: str = PaymentStatus.CREATED.value, - payment_date: datetime = datetime.now(), + payment_date: datetime = datetime.now(tz=timezone.utc), invoice_number: str = None, payment_account_id: int = None, invoice_amount: float = None, @@ -86,7 +114,7 @@ def factory_invoice(payment_account: PaymentAccount, status_code: str = InvoiceS business_identifier: str = 'CP0001234', service_fees: float = 0.0, total=0, paid=0, payment_method_code: str = PaymentMethod.DIRECT_PAY.value, - created_on: datetime = datetime.now(), + created_on: datetime = datetime.now(tz=timezone.utc), cfs_account_id: int = 0, routing_slip=None, disbursement_status_code=None @@ -136,11 +164,13 @@ def factory_payment_line_item(invoice_id: str, fee_schedule_id: int, filing_fees def factory_invoice_reference(invoice_id: int, invoice_number: str = '10021', - status_code=InvoiceReferenceStatus.ACTIVE.value): + status_code=InvoiceReferenceStatus.ACTIVE.value, + is_consolidated=False): """Return Factory.""" return InvoiceReference(invoice_id=invoice_id, status_code=status_code, - invoice_number=invoice_number).save() + invoice_number=invoice_number, + is_consolidated=is_consolidated).save() def factory_create_online_banking_account(auth_account_id='1234', status=CfsAccountStatus.PENDING.value, @@ -158,7 +188,7 @@ def factory_create_pad_account(auth_account_id='1234', bank_number='001', bank_b status=CfsAccountStatus.PENDING.value, payment_method=PaymentMethod.PAD.value, confirmation_period: int = 3): """Return Factory.""" - date_after_wait_period = datetime.today() + timedelta(confirmation_period) + date_after_wait_period = datetime.now(tz=timezone.utc) + timedelta(confirmation_period) account = PaymentAccount(auth_account_id=auth_account_id, payment_method=payment_method, pad_activation_date=date_after_wait_period, @@ -181,7 +211,7 @@ def factory_routing_slip_account( status: str = CfsAccountStatus.PENDING.value, total: int = 0, remaining_amount: int = 0, - routing_slip_date=datetime.now(), + routing_slip_date=datetime.now(tz=timezone.utc), payment_method=PaymentMethod.CASH.value, auth_account_id='1234', routing_slip_status=RoutingSlipStatus.ACTIVE.value, @@ -236,7 +266,7 @@ def factory_create_eft_shortname(short_name: str): def factory_eft_shortname_link(short_name_id: int, auth_account_id: str = '1234', - updated_by: str = None, updated_on: datetime = datetime.now(), + updated_by: str = None, updated_on: datetime = datetime.now(tz=timezone.utc), status_code: str = EFTShortnameStatus.LINKED.value): """Return an EFT short name link model.""" return EFTShortnameLinks( @@ -249,15 +279,13 @@ def factory_eft_shortname_link(short_name_id: int, auth_account_id: str = '1234' ).save() -def factory_create_eft_credit(amount=100, remaining_amount=0, eft_file_id=1, short_name_id=1, payment_account_id=1, - eft_transaction_id=1): +def factory_create_eft_credit(amount=100, remaining_amount=0, eft_file_id=1, short_name_id=1, eft_transaction_id=1): """Return Factory.""" eft_credit = EFTCredit( amount=amount, remaining_amount=remaining_amount, eft_file_id=eft_file_id, short_name_id=short_name_id, - payment_account_id=payment_account_id, eft_transaction_id=eft_transaction_id ).save() return eft_credit @@ -284,18 +312,40 @@ def factory_create_eft_transaction(file_id=1, line_number=1, line_type='T', return eft_transaction -def factory_create_eft_credit_invoice_link(invoice_id=1, eft_credit_id=1, status_code='PENDING', amount=10): +def factory_create_eft_credit_invoice_link(invoice_id=1, eft_credit_id=1, status_code='PENDING', amount=10, + link_group_id=1): """Return Factory.""" eft_credit_invoice_link = EFTCreditInvoiceLink( amount=amount, invoice_id=invoice_id, eft_credit_id=eft_credit_id, receipt_number='1234', - status_code=status_code + status_code=status_code, + link_group_id=link_group_id ).save() return eft_credit_invoice_link +def factory_create_eft_shortname_historical(payment_account_id=1, related_group_link_id=1, short_name_id=1, + statement_number=123, + transaction_type=EFTHistoricalTypes.STATEMENT_PAID.value): + """Return Factory.""" + eft_historical = EFTShortnamesHistorical( + amount=100, + created_by='TEST USER', + credit_balance=100, + hidden=True, + is_processing=True, + payment_account_id=payment_account_id, + related_group_link_id=related_group_link_id, + short_name_id=short_name_id, + statement_number=statement_number, + transaction_date=datetime.now(tz=timezone.utc), + transaction_type=transaction_type + ).save() + return eft_historical + + def factory_create_account(auth_account_id: str = '1234', payment_method_code: str = PaymentMethod.DIRECT_PAY.value, status: str = CfsAccountStatus.PENDING.value, statement_notification_enabled: bool = True): """Return payment account model.""" @@ -324,20 +374,11 @@ def factory_create_ejv_account(auth_account_id='1234', stob=stob, project_code=project_code, account_id=account.id, - start_date=datetime.today().date(), + start_date=datetime.now(tz=timezone.utc).date(), created_by='test').save() return account -def factory_create_wire_account(auth_account_id='1234', status=CfsAccountStatus.PENDING.value): - """Return Factory.""" - account = PaymentAccount(auth_account_id=auth_account_id, - payment_method=PaymentMethod.WIRE.value, - name=f'Test {auth_account_id}').save() - CfsAccount(status=status, account_id=account.id, payment_method=PaymentMethod.WIRE.value).save() - return account - - def factory_distribution(name: str, client: str = '111', reps_centre: str = '22222', service_line: str = '33333', stob: str = '4444', project_code: str = '5555555', service_fee_dist_id: int = None, disbursement_dist_id: int = None): @@ -350,7 +391,7 @@ def factory_distribution(name: str, client: str = '111', reps_centre: str = '222 project_code=project_code, service_fee_distribution_code_id=service_fee_dist_id, disbursement_distribution_code_id=disbursement_dist_id, - start_date=datetime.today().date(), + start_date=datetime.now(tz=timezone.utc).date(), created_by='test').save() @@ -363,7 +404,7 @@ def factory_distribution_link(distribution_code_id: int, fee_schedule_id: int): def factory_receipt( invoice_id: int, receipt_number: str = 'TEST1234567890', - receipt_date: datetime = datetime.now(), + receipt_date: datetime = datetime.now(tz=timezone.utc), receipt_amount: float = 10.0 ): """Return Factory.""" @@ -382,7 +423,7 @@ def factory_refund( """Return Factory.""" return Refund( routing_slip_id=routing_slip_id, - requested_date=datetime.now(), + requested_date=datetime.now(tz=timezone.utc), reason='TEST', requested_by='TEST', details=details @@ -396,7 +437,7 @@ def factory_refund_invoice( """Return Factory.""" return Refund( invoice_id=invoice_id, - requested_date=datetime.now(), + requested_date=datetime.now(tz=timezone.utc), reason='TEST', requested_by='TEST', details=details @@ -408,7 +449,7 @@ def factory_refund_partial( refund_amount: float, refund_type: str, created_by='test', - created_on: datetime = datetime.now() + created_on: datetime = datetime.now(tz=timezone.utc) ): """Return Factory.""" return RefundsPartial( @@ -418,3 +459,36 @@ def factory_refund_partial( created_by=created_by, created_on=created_on ).save() + + +def factory_pad_account_payload(account_id: int = randrange(999999), bank_number: str = '001', + transit_number='999', + bank_account='1234567890'): + """Return a pad payment account object.""" + return { + 'accountId': account_id, + 'accountName': 'Test Account', + 'paymentInfo': { + 'methodOfPayment': PaymentMethod.PAD.value, + 'billable': True, + 'bankTransitNumber': transit_number, + 'bankInstitutionNumber': bank_number, + 'bankAccountNumber': bank_account + } + } + + +def factory_eft_account_payload(payment_method: str = PaymentMethod.EFT.value, + account_id: int = randrange(999999)): + """Return a premium eft enable payment account object.""" + return { + 'accountId': account_id, + 'accountName': 'Test Account', + 'bcolAccountNumber': '2000000', + 'bcolUserId': 'U100000', + 'eft_enable': False, + 'paymentInfo': { + 'methodOfPayment': payment_method, + 'billable': True + } + } diff --git a/jobs/payment-jobs/tests/jobs/test_activate_pad_account_task.py b/jobs/payment-jobs/tests/jobs/test_activate_pad_account_task.py index a0824492b..615381995 100644 --- a/jobs/payment-jobs/tests/jobs/test_activate_pad_account_task.py +++ b/jobs/payment-jobs/tests/jobs/test_activate_pad_account_task.py @@ -16,7 +16,7 @@ Test-Suite to ensure that the CreateAccountTask is working as expected. """ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from flask import current_app from freezegun import freeze_time @@ -51,7 +51,7 @@ def test_activate_pad_accounts_with_time_check(session): 'Same day Job runs and shouldnt change anything.' time_delay = current_app.config['PAD_CONFIRMATION_PERIOD_IN_DAYS'] - with freeze_time(datetime.today() + timedelta(days=time_delay, minutes=1)): + with freeze_time(datetime.now(tz=timezone.utc) + timedelta(days=time_delay, minutes=1)): ActivatePadAccountTask.activate_pad_accounts() account: PaymentAccount = PaymentAccount.find_by_id(account.id) cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) @@ -78,7 +78,7 @@ def test_activate_bcol_change_to_pad(session): assert account.payment_method == PaymentMethod.DRAWDOWN.value time_delay = current_app.config['PAD_CONFIRMATION_PERIOD_IN_DAYS'] - with freeze_time(datetime.today() + timedelta(days=time_delay, minutes=1)): + with freeze_time(datetime.now(tz=timezone.utc) + timedelta(days=time_delay, minutes=1)): ActivatePadAccountTask.activate_pad_accounts() assert cfs_account.status == CfsAccountStatus.ACTIVE.value, \ 'After the confirmation period is over , status should be active' diff --git a/jobs/payment-jobs/tests/jobs/test_cfs_create_invoice_task.py b/jobs/payment-jobs/tests/jobs/test_cfs_create_invoice_task.py index 0f9d519aa..159cd1acf 100644 --- a/jobs/payment-jobs/tests/jobs/test_cfs_create_invoice_task.py +++ b/jobs/payment-jobs/tests/jobs/test_cfs_create_invoice_task.py @@ -46,7 +46,7 @@ def test_create_pad_invoice_single_transaction(session): """Assert PAD invoices are created.""" # Create an account and an invoice for the account account = factory_create_pad_account(auth_account_id='1', status=CfsAccountStatus.ACTIVE.value) - previous_day = datetime.now() - timedelta(days=1) + previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, status_code=InvoiceStatus.APPROVED.value, payment_method_code=None) @@ -70,7 +70,7 @@ def test_create_pad_invoice_mixed_pli_values(session): """Assert PAD invoices are created with total = 0, service fees > 0.""" # Create an account and an invoice for the account account = factory_create_pad_account(auth_account_id='1', status=CfsAccountStatus.ACTIVE.value) - previous_day = datetime.now() - timedelta(days=1) + previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=1.5, status_code=InvoiceStatus.APPROVED.value, payment_method_code=None) @@ -101,7 +101,7 @@ def test_create_rs_invoice_single_transaction(session): # Create an account and an invoice for the account rs_number = '123' account = factory_routing_slip_account(number=rs_number, status=CfsAccountStatus.ACTIVE.value) - previous_day = datetime.now() - timedelta(days=1) + previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, status_code=InvoiceStatus.APPROVED.value, @@ -147,7 +147,7 @@ def test_create_pad_invoice_single_transaction_run_again(session): """Assert PAD invoices are created.""" # Create an account and an invoice for the account account = factory_create_pad_account(auth_account_id='1', status=CfsAccountStatus.ACTIVE.value) - previous_day = datetime.now() - timedelta(days=1) + previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, status_code=InvoiceStatus.APPROVED.value, payment_method_code=None) @@ -179,7 +179,7 @@ def test_create_pad_invoice_for_frozen_accounts(session): """Assert PAD invoices are created.""" # Create an account and an invoice for the account account = factory_create_pad_account(auth_account_id='1', status=CfsAccountStatus.FREEZE.value) - previous_day = datetime.now() - timedelta(days=1) + previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, status_code=InvoiceStatus.APPROVED.value, payment_method_code=None) @@ -204,7 +204,7 @@ def test_create_pad_invoice_multiple_transactions(session): """Assert PAD invoices are created.""" # Create an account and an invoice for the account account = factory_create_pad_account(auth_account_id='1', status=CfsAccountStatus.ACTIVE.value) - previous_day = datetime.now() - timedelta(days=1) + previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, status_code=InvoiceStatus.APPROVED.value, payment_method_code=None) @@ -229,7 +229,7 @@ def test_create_pad_invoice_before_cutoff(session): """Assert PAD invoices are created.""" # Create an account and an invoice for the account account = factory_create_pad_account(auth_account_id='1', status=CfsAccountStatus.ACTIVE.value) - previous_day = datetime.now() - timedelta(days=2) + previous_day = datetime.now(tz=timezone.utc) - timedelta(days=2) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, status_code=InvoiceStatus.APPROVED.value, payment_method_code=None) @@ -254,7 +254,7 @@ def test_create_online_banking_transaction(session): """Assert Online Banking invoices are created.""" # Create an account and an invoice for the account account = factory_create_online_banking_account(auth_account_id='1', status=CfsAccountStatus.ACTIVE.value) - previous_day = datetime.now() - timedelta(days=1) + previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, payment_method_code=None) @@ -280,12 +280,12 @@ def test_create_eft_invoice(session): previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, - status_code=InvoiceStatus.CREATED.value, payment_method_code=PaymentMethod.EFT.value) + status_code=InvoiceStatus.APPROVED.value, payment_method_code=PaymentMethod.EFT.value) fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type('CP', 'OTANN') line = factory_payment_line_item(invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() - assert invoice.invoice_status_code == InvoiceStatus.CREATED.value + assert invoice.invoice_status_code == InvoiceStatus.APPROVED.value CreateInvoiceTask.create_invoices() @@ -294,7 +294,7 @@ def test_create_eft_invoice(session): find_by_invoice_id_and_status(invoice.id, InvoiceReferenceStatus.ACTIVE.value) assert invoice_reference - assert updated_invoice.invoice_status_code == InvoiceStatus.CREATED.value + assert updated_invoice.invoice_status_code == InvoiceStatus.APPROVED.value def test_create_eft_invoice_rerun(session): @@ -303,14 +303,14 @@ def test_create_eft_invoice_rerun(session): previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, - status_code=InvoiceStatus.CREATED.value, payment_method_code=PaymentMethod.EFT.value) + status_code=InvoiceStatus.APPROVED.value, payment_method_code=PaymentMethod.EFT.value) fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type('CP', 'OTANN') line = factory_payment_line_item(invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() invoice_response = {'invoice_number': '10021', 'pbc_ref_number': '10005', 'party_number': '11111', 'party_name': 'invoice'} - assert invoice.invoice_status_code == InvoiceStatus.CREATED.value + assert invoice.invoice_status_code == InvoiceStatus.APPROVED.value with patch.object(CFSService, 'create_account_invoice', return_value=invoice_response) as mock_cfs: CreateInvoiceTask.create_invoices() @@ -321,7 +321,7 @@ def test_create_eft_invoice_rerun(session): find_by_invoice_id_and_status(invoice.id, InvoiceReferenceStatus.ACTIVE.value) assert inv_ref - assert updated_invoice.invoice_status_code == InvoiceStatus.CREATED.value + assert updated_invoice.invoice_status_code == InvoiceStatus.APPROVED.value with patch.object(CFSService, 'create_account_invoice', return_value=invoice_response) as mock_cfs: CreateInvoiceTask.create_invoices() @@ -334,13 +334,13 @@ def test_create_eft_invoice_on_frozen_account(session): previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, - status_code=InvoiceStatus.CREATED.value, payment_method_code=PaymentMethod.EFT.value) + status_code=InvoiceStatus.APPROVED.value, payment_method_code=PaymentMethod.EFT.value) fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type('CP', 'OTANN') line = factory_payment_line_item(invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() - assert invoice.invoice_status_code == InvoiceStatus.CREATED.value + assert invoice.invoice_status_code == InvoiceStatus.APPROVED.value CreateInvoiceTask.create_invoices() @@ -349,7 +349,7 @@ def test_create_eft_invoice_on_frozen_account(session): find_by_invoice_id_and_status(invoice.id, InvoiceReferenceStatus.ACTIVE.value) assert inv_ref is None - assert updated_invoice.invoice_status_code == InvoiceStatus.CREATED.value + assert updated_invoice.invoice_status_code == InvoiceStatus.APPROVED.value def test_create_eft_invoices(session): @@ -358,14 +358,14 @@ def test_create_eft_invoices(session): previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, - status_code=InvoiceStatus.CREATED.value, payment_method_code=PaymentMethod.EFT.value) + status_code=InvoiceStatus.APPROVED.value, payment_method_code=PaymentMethod.EFT.value) fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type('CP', 'OTANN') line = factory_payment_line_item(invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() # Create another invoice for this account invoice2 = factory_invoice(payment_account=account, created_on=previous_day, total=10, - status_code=InvoiceStatus.CREATED.value, payment_method_code=PaymentMethod.EFT.value) + status_code=InvoiceStatus.APPROVED.value, payment_method_code=PaymentMethod.EFT.value) fee_schedule2 = FeeScheduleModel.find_by_filing_type_and_corp_type('CP', 'OTADD') line2 = factory_payment_line_item(invoice2.id, fee_schedule_id=fee_schedule2.fee_schedule_id) line2.save() @@ -373,7 +373,7 @@ def test_create_eft_invoices(session): CreateInvoiceTask.create_invoices() invoice2 = InvoiceModel.find_by_id(invoice2.id) invoice = InvoiceModel.find_by_id(invoice.id) - assert invoice2.invoice_status_code == invoice.invoice_status_code == InvoiceStatus.CREATED.value + assert invoice2.invoice_status_code == invoice.invoice_status_code == InvoiceStatus.APPROVED.value def test_create_eft_invoice_before_cutoff(session): @@ -382,12 +382,12 @@ def test_create_eft_invoice_before_cutoff(session): previous_day = datetime.now(tz=timezone.utc) - timedelta(days=2) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, - status_code=InvoiceStatus.CREATED.value, payment_method_code=PaymentMethod.EFT.value) + status_code=InvoiceStatus.APPROVED.value, payment_method_code=PaymentMethod.EFT.value) fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type('CP', 'OTANN') line = factory_payment_line_item(invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() - assert invoice.invoice_status_code == InvoiceStatus.CREATED.value + assert invoice.invoice_status_code == InvoiceStatus.APPROVED.value CreateInvoiceTask.create_invoices() @@ -396,4 +396,4 @@ def test_create_eft_invoice_before_cutoff(session): find_by_invoice_id_and_status(invoice.id, InvoiceReferenceStatus.ACTIVE.value) assert inv_ref is not None # As EFT will be summed up for all outstanding invoices - assert updated_invoice.invoice_status_code == InvoiceStatus.CREATED.value + assert updated_invoice.invoice_status_code == InvoiceStatus.APPROVED.value diff --git a/jobs/payment-jobs/tests/jobs/test_direct_pay_automated_refund_task.py b/jobs/payment-jobs/tests/jobs/test_direct_pay_automated_refund_task.py index f7dc306d5..6bae89034 100644 --- a/jobs/payment-jobs/tests/jobs/test_direct_pay_automated_refund_task.py +++ b/jobs/payment-jobs/tests/jobs/test_direct_pay_automated_refund_task.py @@ -85,7 +85,8 @@ def payment_status(cls): # pylint: disable=unused-argument; mocks of library me target = 'tasks.direct_pay_automated_refund_task.DirectPayAutomatedRefundTask._query_order_status' monkeypatch.setattr(target, payment_status) - with freeze_time(datetime.datetime.combine(datetime.datetime.utcnow().date(), datetime.time(6, 00))): + with freeze_time(datetime.datetime.combine(datetime.datetime.now(tz=datetime.timezone.utc).date(), + datetime.time(6, 00))): DirectPayAutomatedRefundTask().process_cc_refunds() refund = RefundModel.find_by_invoice_id(invoice.id) assert invoice.invoice_status_code == InvoiceStatus.REFUNDED.value @@ -134,7 +135,8 @@ def payment_status(cls): # pylint: disable=unused-argument; mocks of library me target = 'tasks.direct_pay_automated_refund_task.DirectPayAutomatedRefundTask._query_order_status' monkeypatch.setattr(target, payment_status) - with freeze_time(datetime.datetime.combine(datetime.datetime.utcnow().date(), datetime.time(6, 00))): + with freeze_time(datetime.datetime.combine(datetime.datetime.now(tz=datetime.timezone.utc).date(), + datetime.time(6, 00))): DirectPayAutomatedRefundTask().process_cc_refunds() assert refund.gl_error == 'BAD BAD' assert refund.gl_posted is None diff --git a/jobs/payment-jobs/tests/jobs/test_eft_task.py b/jobs/payment-jobs/tests/jobs/test_eft_task.py index 98c585a9d..98a6711a3 100644 --- a/jobs/payment-jobs/tests/jobs/test_eft_task.py +++ b/jobs/payment-jobs/tests/jobs/test_eft_task.py @@ -16,20 +16,25 @@ Test-Suite to ensure that the EFTTask for electronic funds transfer is working as expected. """ -import pytest +from datetime import datetime, timezone from unittest.mock import patch +import pytest + from pay_api.models import CfsAccount as CfsAccountModel from pay_api.models import FeeSchedule as FeeScheduleModel from pay_api.models import Receipt as ReceiptModel from pay_api.utils.enums import ( - CfsAccountStatus, DisbursementStatus, EFTCreditInvoiceStatus, InvoiceReferenceStatus, InvoiceStatus, PaymentMethod) + CfsAccountStatus, DisbursementStatus, EFTCreditInvoiceStatus, EFTHistoricalTypes, InvoiceReferenceStatus, + InvoiceStatus, PaymentMethod) + from tasks.eft_task import EFTTask from .factory import ( factory_create_eft_account, factory_create_eft_credit, factory_create_eft_credit_invoice_link, - factory_create_eft_file, factory_create_eft_shortname, factory_create_eft_transaction, factory_invoice, - factory_invoice_reference, factory_payment, factory_payment_line_item, factory_receipt) + factory_create_eft_file, factory_create_eft_shortname, factory_create_eft_shortname_historical, + factory_create_eft_transaction, factory_invoice, factory_invoice_reference, factory_payment, + factory_payment_line_item, factory_receipt) def setup_eft_credit_invoice_links_test(): @@ -42,25 +47,34 @@ def setup_eft_credit_invoice_links_test(): tests = [ - ('happy_flow', PaymentMethod.EFT.value, [InvoiceStatus.CREATED.value, InvoiceStatus.PAID.value], + ('invoice_refund_flow', PaymentMethod.EFT.value, [InvoiceStatus.REFUND_REQUESTED.value], + [EFTCreditInvoiceStatus.CANCELLED.value], [None], 0, 0), + ('insufficient_amount_on_links', PaymentMethod.EFT.value, [InvoiceStatus.APPROVED.value, InvoiceStatus.PAID.value], + [EFTCreditInvoiceStatus.PENDING.value, EFTCreditInvoiceStatus.PENDING_REFUND.value], [None], 0, 0), + ('happy_flow_multiple_links', PaymentMethod.EFT.value, [InvoiceStatus.APPROVED.value, InvoiceStatus.PAID.value], + [EFTCreditInvoiceStatus.PENDING.value, EFTCreditInvoiceStatus.PENDING_REFUND.value], + [None, DisbursementStatus.COMPLETED.value], 1, 2), + ('happy_flow', PaymentMethod.EFT.value, [InvoiceStatus.APPROVED.value, InvoiceStatus.PAID.value], [EFTCreditInvoiceStatus.PENDING.value, EFTCreditInvoiceStatus.PENDING_REFUND.value], [None, DisbursementStatus.COMPLETED.value], 1, 2), ('duplicate_active_cfs_account', PaymentMethod.EFT.value, [ - InvoiceStatus.CREATED.value, InvoiceStatus.PAID.value], [EFTCreditInvoiceStatus.PENDING.value, - EFTCreditInvoiceStatus.PENDING_REFUND.value], + InvoiceStatus.APPROVED.value, InvoiceStatus.PAID.value], [EFTCreditInvoiceStatus.PENDING.value, + EFTCreditInvoiceStatus.PENDING_REFUND.value], [None], 1, 1), ('no_cfs_active', PaymentMethod.EFT.value, [ - InvoiceStatus.CREATED.value], [EFTCreditInvoiceStatus.PENDING.value], [None], 0, 0), + InvoiceStatus.APPROVED.value], [EFTCreditInvoiceStatus.PENDING.value], [None], 0, 0), ('wrong_payment_method', PaymentMethod.PAD.value, [ InvoiceStatus.CREATED.value], [EFTCreditInvoiceStatus.PENDING.value], [None], 0, 0), ('credit_invoice_link_status_incorrect', PaymentMethod.EFT.value, [ - InvoiceStatus.CREATED.value], [EFTCreditInvoiceStatus.COMPLETED.value, EFTCreditInvoiceStatus.REFUNDED.value], + InvoiceStatus.APPROVED.value], [EFTCreditInvoiceStatus.COMPLETED.value, EFTCreditInvoiceStatus.REFUNDED.value], [None], 0, 0), ('wrong_disbursement', PaymentMethod.EFT.value, [ - InvoiceStatus.CREATED.value], [EFTCreditInvoiceStatus.PENDING.value], [DisbursementStatus.UPLOADED.value], 0, 0), + InvoiceStatus.APPROVED.value], [EFTCreditInvoiceStatus.PENDING.value], [DisbursementStatus.UPLOADED.value], 0, 0), ('wrong_invoice_status', PaymentMethod.EFT.value, [ - InvoiceStatus.CREDITED.value, InvoiceStatus.PARTIAL.value, InvoiceStatus.APPROVED.value], - [EFTCreditInvoiceStatus.PENDING.value], [None], 0, 0) + InvoiceStatus.CREDITED.value, InvoiceStatus.PARTIAL.value, InvoiceStatus.CREATED.value], + [EFTCreditInvoiceStatus.PENDING.value], [None], 0, 0), + ('no_invoice_reference', PaymentMethod.EFT.value, [ + InvoiceStatus.APPROVED.value], [EFTCreditInvoiceStatus.PENDING.value], [None], 0, 0), ] @@ -82,8 +96,8 @@ def test_eft_credit_invoice_links_by_status(session, test_name, payment_method, CfsAccountModel.find_by_account_id(payment_account.id)[0].status = CfsAccountStatus.INACTIVE.value eft_credit = factory_create_eft_credit( - amount=100, remaining_amount=0, eft_file_id=eft_file.id, short_name_id=short_name_id, - payment_account_id=payment_account.id, eft_transaction_id=eft_transaction_id) + amount=100, remaining_amount=0, eft_file_id=eft_file.id, short_name_id=short_name_id, + eft_transaction_id=eft_transaction_id) for invoice_status in invoice_status_codes: for disbursement_status in disbursement_status_codes: @@ -92,53 +106,129 @@ def test_eft_credit_invoice_links_by_status(session, test_name, payment_method, payment_method_code=payment_method, status_code=invoice_status, disbursement_status_code=disbursement_status) - factory_invoice_reference(invoice_id=invoice.id) - factory_create_eft_credit_invoice_link( - invoice_id=invoice.id, eft_credit_id=eft_credit.id, status_code=eft_credit_invoice_status) + if test_name != 'no_invoice_reference': + factory_invoice_reference(invoice_id=invoice.id) + match test_name: + case 'happy_flow_multiple_links': + factory_create_eft_credit_invoice_link( + invoice_id=invoice.id, + eft_credit_id=eft_credit.id, + status_code=eft_credit_invoice_status, + amount=invoice.total / 2) + factory_create_eft_credit_invoice_link( + invoice_id=invoice.id, + eft_credit_id=eft_credit.id, + status_code=eft_credit_invoice_status, + amount=invoice.total / 2) + case 'insufficient_amount_on_links': + factory_create_eft_credit_invoice_link( + invoice_id=invoice.id, + eft_credit_id=eft_credit.id, + status_code=eft_credit_invoice_status, + amount=invoice.total - 1) + case _: + factory_create_eft_credit_invoice_link( + invoice_id=invoice.id, eft_credit_id=eft_credit.id, status_code=eft_credit_invoice_status, + amount=invoice.total) results = EFTTask.get_eft_credit_invoice_links_by_status(EFTCreditInvoiceStatus.PENDING.value) if max_cfs_account_id: - for invoice, _, cfs_account in results: + for invoice, cfs_account, _ in results: assert cfs_account.id == max_cfs_account_id assert len(results) == pending_count results = EFTTask.get_eft_credit_invoice_links_by_status(EFTCreditInvoiceStatus.PENDING_REFUND.value) assert len(results) == pending_refund_count + if test_name == 'invoice_refund_flow': + results = EFTTask.get_eft_credit_invoice_links_by_status(EFTCreditInvoiceStatus.CANCELLED.value) + assert len(results) == 1 -def test_link_electronic_funds_transfers(session): +@pytest.mark.parametrize('test_name', ('happy_path', 'consolidated_happy', 'consolidated_mismatch', + 'normal_invoice_missing')) +def test_link_electronic_funds_transfers(session, test_name): """Test link electronic funds transfers.""" auth_account_id, eft_file, short_name_id, eft_transaction_id = setup_eft_credit_invoice_links_test() payment_account = factory_create_eft_account(auth_account_id=auth_account_id, status=CfsAccountStatus.ACTIVE.value) - invoice = factory_invoice(payment_account=payment_account, payment_method_code=PaymentMethod.EFT.value) + invoice = factory_invoice(payment_account=payment_account, payment_method_code=PaymentMethod.EFT.value, + status_code=InvoiceStatus.APPROVED.value, total=10) invoice_reference = factory_invoice_reference(invoice_id=invoice.id) factory_payment(payment_account_id=payment_account.id, payment_method_code=PaymentMethod.EFT.value, invoice_amount=351.50) eft_credit = factory_create_eft_credit( amount=100, remaining_amount=0, eft_file_id=eft_file.id, short_name_id=short_name_id, - payment_account_id=payment_account.id, eft_transaction_id=eft_transaction_id) - credit_invoice_link = factory_create_eft_credit_invoice_link(invoice_id=invoice.id, eft_credit_id=eft_credit.id) + eft_transaction_id=eft_transaction_id) + credit_invoice_link = factory_create_eft_credit_invoice_link(invoice_id=invoice.id, eft_credit_id=eft_credit.id, + link_group_id=1, amount=5) + credit_invoice_link2 = factory_create_eft_credit_invoice_link(invoice_id=invoice.id, eft_credit_id=eft_credit.id, + link_group_id=1, amount=5) + eft_historical = factory_create_eft_shortname_historical( + payment_account_id=payment_account.id, + short_name_id=short_name_id, + related_group_link_id=1 + ) + assert eft_historical.hidden + assert eft_historical.is_processing cfs_account = CfsAccountModel.find_effective_by_payment_method( payment_account.id, PaymentMethod.EFT.value) + return_value = {} + original_invoice_reference = None - with patch('pay_api.services.CFSService.create_cfs_receipt') as mock_create_cfs: - EFTTask.link_electronic_funds_transfers_cfs() - mock_create_cfs.assert_called() + match test_name: + case 'consolidated_happy' | 'consolidated_mismatch': + invoice_reference.is_consolidated = True + invoice_reference.save() + original_invoice_reference = factory_invoice_reference(invoice_id=invoice.id, + is_consolidated=False, + status_code=InvoiceReferenceStatus.CANCELLED.value) \ + .save() + return_value = {'total': 10.00} + if test_name == 'consolidated_mismatch': + return_value = {'total': 10.01} + case 'normal_invoice_missing': + invoice_reference.is_consolidated = True + invoice_reference.save() + case _: + pass + + if test_name in ['consolidated_mismatch', 'normal_invoice_missing']: + with patch('pay_api.services.CFSService.get_invoice', return_value=return_value) as mock_get_invoice: + EFTTask.link_electronic_funds_transfers_cfs() + # No change, the amount didn't match or normal invoice was missing. + assert invoice_reference.status_code == InvoiceReferenceStatus.ACTIVE.value + return + + with patch('pay_api.services.CFSService.reverse_invoice') as mock_reverse_invoice: + with patch('pay_api.services.CFSService.create_cfs_receipt') as mock_create_receipt: + with patch('pay_api.services.CFSService.get_invoice', return_value=return_value) as mock_get_invoice: + EFTTask.link_electronic_funds_transfers_cfs() + if test_name == 'consolidated_happy': + mock_reverse_invoice.assert_called() + mock_get_invoice.assert_called() + mock_create_receipt.assert_called() assert cfs_account.status == CfsAccountStatus.ACTIVE.value - assert invoice_reference.status_code == InvoiceReferenceStatus.COMPLETED.value + if test_name == 'consolidated_happy': + assert invoice_reference.status_code == InvoiceReferenceStatus.CANCELLED.value + assert original_invoice_reference.status_code == InvoiceReferenceStatus.COMPLETED.value + else: + assert invoice_reference.status_code == InvoiceReferenceStatus.COMPLETED.value receipt = ReceiptModel.find_all_receipts_for_invoice(invoice.id)[0] assert receipt - assert receipt.receipt_amount == credit_invoice_link.amount + assert receipt.receipt_amount == credit_invoice_link.amount + credit_invoice_link2.amount assert receipt.invoice_id == invoice.id assert invoice.invoice_status_code == InvoiceStatus.PAID.value - assert invoice.paid == credit_invoice_link.amount + assert invoice.paid == credit_invoice_link.amount + credit_invoice_link2.amount assert invoice.payment_date assert credit_invoice_link.status_code == EFTCreditInvoiceStatus.COMPLETED.value + assert credit_invoice_link2.status_code == EFTCreditInvoiceStatus.COMPLETED.value + + assert not eft_historical.hidden + assert not eft_historical.is_processing -def test_unlink_electronic_funds_transfers(session): - """Test unlink electronic funds transfers.""" +def test_reverse_electronic_funds_transfers(session): + """Test reverse electronic funds transfers.""" auth_account_id, eft_file, short_name_id, eft_transaction_id = setup_eft_credit_invoice_links_test() receipt_number = '1111R' invoice_number = '1234' @@ -157,23 +247,151 @@ def test_unlink_electronic_funds_transfers(session): invoice_number=invoice_number) eft_credit = factory_create_eft_credit( amount=100, remaining_amount=0, eft_file_id=eft_file.id, short_name_id=short_name_id, - payment_account_id=payment_account.id, eft_transaction_id=eft_transaction_id) cil = factory_create_eft_credit_invoice_link(invoice_id=invoice.id, status_code=EFTCreditInvoiceStatus.PENDING_REFUND.value, - eft_credit_id=eft_credit.id) + eft_credit_id=eft_credit.id, + amount=30) factory_receipt(invoice.id, receipt_number) + refund_requested_invoice = factory_invoice(payment_account=payment_account, total=30, + status_code=InvoiceStatus.REFUND_REQUESTED.value, + payment_method_code=PaymentMethod.EFT.value) + + cil2 = factory_create_eft_credit_invoice_link(invoice_id=refund_requested_invoice.id, + status_code=EFTCreditInvoiceStatus.CANCELLED.value, + eft_credit_id=eft_credit.id, + amount=30) + invoice_reference2 = factory_invoice_reference(invoice_id=refund_requested_invoice.id, + status_code=InvoiceReferenceStatus.ACTIVE.value, + invoice_number=invoice_number) + refund_requested_invoice2 = factory_invoice(payment_account=payment_account, total=30, + status_code=InvoiceStatus.REFUND_REQUESTED.value, + payment_method_code=PaymentMethod.EFT.value) + + cil3 = factory_create_eft_credit_invoice_link(invoice_id=refund_requested_invoice2.id, + status_code=EFTCreditInvoiceStatus.PENDING_REFUND.value, + eft_credit_id=eft_credit.id, + amount=30) + invoice_reference3 = factory_invoice_reference(invoice_id=refund_requested_invoice2.id, + status_code=InvoiceReferenceStatus.COMPLETED.value, + invoice_number=invoice_number) + + refund_requested_no_ref = factory_invoice(payment_account=payment_account, total=30, + status_code=InvoiceStatus.REFUND_REQUESTED.value, + payment_method_code=PaymentMethod.EFT.value) + cil4 = factory_create_eft_credit_invoice_link(invoice_id=refund_requested_no_ref.id, + status_code=EFTCreditInvoiceStatus.CANCELLED.value, + eft_credit_id=eft_credit.id, + amount=30) + eft_historical = factory_create_eft_shortname_historical( + payment_account_id=payment_account.id, + short_name_id=short_name_id, + related_group_link_id=1, + transaction_type=EFTHistoricalTypes.STATEMENT_REVERSE.value + ) + assert eft_historical.hidden + assert eft_historical.is_processing + session.commit() with patch('pay_api.services.CFSService.reverse_rs_receipt_in_cfs') as mock_reverse: - EFTTask.reverse_electronic_funds_transfers_cfs() - mock_reverse.assert_called() + with patch('pay_api.services.CFSService.reverse_invoice') as mock_invoice: + EFTTask.reverse_electronic_funds_transfers_cfs() + mock_invoice.assert_called() + mock_reverse.assert_called() assert invoice_reference.status_code == InvoiceReferenceStatus.ACTIVE.value assert len(ReceiptModel.find_all_receipts_for_invoice(invoice.id)) == 0 - assert invoice.invoice_status_code == InvoiceStatus.CREATED.value + assert invoice.invoice_status_code == InvoiceStatus.APPROVED.value assert invoice.paid == 0 - assert invoice.refund == cil.amount - assert invoice.refund_date + assert invoice.payment_date is None assert cil.status_code == EFTCreditInvoiceStatus.REFUNDED.value + + assert not eft_historical.hidden + assert not eft_historical.is_processing + + assert refund_requested_invoice.invoice_status_code == InvoiceStatus.REFUNDED.value + assert invoice_reference2.status_code == InvoiceReferenceStatus.CANCELLED.value + assert refund_requested_invoice2.invoice_status_code == InvoiceStatus.REFUNDED.value + assert invoice_reference3.status_code == InvoiceReferenceStatus.CANCELLED.value + assert cil.status_code == EFTCreditInvoiceStatus.REFUNDED.value + assert cil2.status_code == EFTCreditInvoiceStatus.CANCELLED.value + assert cil3.status_code == EFTCreditInvoiceStatus.REFUNDED.value + assert cil4.status_code == EFTCreditInvoiceStatus.CANCELLED.value + + +def test_unlock_overdue_accounts(session): + """Test unlock overdue account events.""" + auth_account_id, eft_file, short_name_id, eft_transaction_id = setup_eft_credit_invoice_links_test() + payment_account = factory_create_eft_account(auth_account_id=auth_account_id, status=CfsAccountStatus.ACTIVE.value) + payment_account.has_overdue_invoices = datetime.now(tz=timezone.utc) + invoice_1 = factory_invoice(payment_account=payment_account, payment_method_code=PaymentMethod.EFT.value, total=10) + invoice_1.invoice_status_code = InvoiceStatus.OVERDUE.value + invoice_1.save() + factory_invoice_reference(invoice_id=invoice_1.id) + eft_credit = factory_create_eft_credit( + amount=100, remaining_amount=0, eft_file_id=eft_file.id, short_name_id=short_name_id, + eft_transaction_id=eft_transaction_id) + factory_create_eft_credit_invoice_link(invoice_id=invoice_1.id, eft_credit_id=eft_credit.id, amount=10) + + # Create second overdue invoice and confirm unlock is not double called on a payment account + invoice_2 = factory_invoice(payment_account=payment_account, payment_method_code=PaymentMethod.EFT.value, total=10) + invoice_2.invoice_status_code = InvoiceStatus.OVERDUE.value + invoice_2.save() + factory_invoice_reference(invoice_id=invoice_2.id) + factory_create_eft_credit_invoice_link(invoice_id=invoice_2.id, eft_credit_id=eft_credit.id, amount=10) + + with patch('utils.auth_event.AuthEvent.publish_unlock_account_event') as mock_unlock: + EFTTask.link_electronic_funds_transfers_cfs() + assert payment_account.has_overdue_invoices is None + mock_unlock.assert_called_once() + mock_unlock.assert_called_with(payment_account) + + +def test_handle_unlinked_refund_requested_invoices(session): + """Test handle unlinked refund requested invoices.""" + auth_account_id, eft_file, short_name_id, eft_transaction_id = setup_eft_credit_invoice_links_test() + eft_credit = factory_create_eft_credit( + amount=100, remaining_amount=0, eft_file_id=eft_file.id, short_name_id=short_name_id, + eft_transaction_id=eft_transaction_id) + payment_account = factory_create_eft_account(auth_account_id=auth_account_id, status=CfsAccountStatus.ACTIVE.value) + invoice_1 = factory_invoice(payment_account=payment_account, status_code=InvoiceStatus.REFUND_REQUESTED.value, + payment_method_code=PaymentMethod.EFT.value, total=10).save() + factory_invoice_reference(invoice_id=invoice_1.id).save() + factory_create_eft_credit_invoice_link(invoice_id=invoice_1.id, eft_credit_id=eft_credit.id, amount=10) + invoice_2 = factory_invoice(payment_account=payment_account, status_code=InvoiceStatus.REFUND_REQUESTED.value, + payment_method_code=PaymentMethod.EFT.value, total=10).save() + invoice_ref_2 = factory_invoice_reference(invoice_id=invoice_2.id).save() + invoice_3 = factory_invoice(payment_account=payment_account, status_code=InvoiceStatus.REFUND_REQUESTED.value, + payment_method_code=PaymentMethod.EFT.value, total=10).save() + with patch('pay_api.services.CFSService.reverse_invoice') as mock_invoice: + EFTTask.handle_unlinked_refund_requested_invoices() + mock_invoice.assert_called() + # Has CIL so it's excluded + assert invoice_1.invoice_status_code == InvoiceStatus.REFUND_REQUESTED.value + # Has no CIL and invoice reference + assert invoice_2.invoice_status_code == InvoiceStatus.REFUNDED.value + assert invoice_2.refund_date + assert invoice_2.refund + assert invoice_ref_2.status_code == InvoiceReferenceStatus.CANCELLED.value + # Has no invoice reference, should still move to REFUNDED + assert invoice_3.invoice_status_code == InvoiceStatus.REFUNDED.value + + +def test_rollback_consolidated_invoice(): + """Ensure we can't rollback a consolidated invoice.""" + payment_account = factory_create_eft_account(status=CfsAccountStatus.ACTIVE.value) + invoice_1 = factory_invoice(payment_account=payment_account).save() + invoice_reference = factory_invoice_reference(invoice_id=invoice_1.id, + status_code=InvoiceReferenceStatus.COMPLETED.value, + is_consolidated=True).save() + with pytest.raises(Exception) as excinfo: + EFTTask._rollback_receipt_and_invoice(None, # pylint: disable=protected-access + invoice_1, + None, + cil_status_code=EFTCreditInvoiceStatus.PENDING_REFUND.value) + assert 'Cannot reverse a consolidated invoice' in excinfo.value.args + with pytest.raises(Exception) as excinfo: + EFTTask._handle_invoice_refund(None, invoice_reference) # pylint: disable=protected-access + assert 'Cannot reverse a consolidated invoice' in excinfo.value.args diff --git a/jobs/payment-jobs/tests/jobs/test_ejv_partner_distribution_task.py b/jobs/payment-jobs/tests/jobs/test_ejv_partner_distribution_task.py index 8d1191db6..913154e10 100644 --- a/jobs/payment-jobs/tests/jobs/test_ejv_partner_distribution_task.py +++ b/jobs/payment-jobs/tests/jobs/test_ejv_partner_distribution_task.py @@ -16,7 +16,7 @@ Test-Suite to ensure that the CgiEjvJob is working as expected. """ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import pytest from flask import current_app @@ -43,6 +43,8 @@ def test_disbursement_for_partners(session, monkeypatch, client_code, batch_type """ monkeypatch.setattr('pysftp.Connection.put', lambda *args, **kwargs: None) corp_type: CorpTypeModel = CorpTypeModel.find_by_code('VS') + corp_type.has_partner_disbursements = True + corp_type.save() pad_account = factory_create_pad_account(auth_account_id='1234', bank_number='001', @@ -74,7 +76,7 @@ def test_disbursement_for_partners(session, monkeypatch, client_code, batch_type inv_ref = factory_invoice_reference(invoice_id=invoice.id) factory_payment(invoice_number=inv_ref.invoice_number, payment_status_code='COMPLETED') - factory_receipt(invoice_id=invoice.id, receipt_date=datetime.today()).save() + factory_receipt(invoice_id=invoice.id, receipt_date=datetime.now(tz=timezone.utc)).save() EjvPartnerDistributionTask.create_ejv_file() @@ -82,7 +84,8 @@ def test_disbursement_for_partners(session, monkeypatch, client_code, batch_type invoice = Invoice.find_by_id(invoice.id) assert invoice.disbursement_status_code is None - day_after_time_delay = datetime.today() + timedelta(days=(current_app.config.get('DISBURSEMENT_DELAY_IN_DAYS') + 1)) + day_after_time_delay = datetime.now(tz=timezone.utc) + \ + timedelta(days=(current_app.config.get('DISBURSEMENT_DELAY_IN_DAYS') + 1)) with freeze_time(day_after_time_delay): EjvPartnerDistributionTask.create_ejv_file() # Lookup invoice and assert disbursement status @@ -105,7 +108,7 @@ def test_disbursement_for_partners(session, monkeypatch, client_code, batch_type invoice.disbursement_status_code = DisbursementStatus.COMPLETED.value ejv_file.disbursement_status_code = DisbursementStatus.COMPLETED.value invoice.invoice_status_code = InvoiceStatus.REFUNDED.value - invoice.refund_date = datetime.now() + invoice.refund_date = datetime.now(tz=timezone.utc) invoice.save() EjvPartnerDistributionTask.create_ejv_file() diff --git a/jobs/payment-jobs/tests/jobs/test_ejv_partner_partial_refund_distribution_task.py b/jobs/payment-jobs/tests/jobs/test_ejv_partner_partial_refund_distribution_task.py index 7ab4cce30..442530de7 100644 --- a/jobs/payment-jobs/tests/jobs/test_ejv_partner_partial_refund_distribution_task.py +++ b/jobs/payment-jobs/tests/jobs/test_ejv_partner_partial_refund_distribution_task.py @@ -16,7 +16,8 @@ Test-Suite to ensure that the CgiEjvJob is working as expected. """ -from datetime import datetime, timedelta +import pytest +from datetime import datetime, timedelta, timezone from flask import current_app from freezegun import freeze_time @@ -31,6 +32,7 @@ factory_invoice_reference, factory_payment, factory_payment_line_item, factory_refund_partial) +@pytest.mark.skip(reason='Will be fixed in future ticket') def test_partial_refund_disbursement(session, monkeypatch): """Test partial refund disbursement.""" monkeypatch.setattr('pysftp.Connection.put', lambda *args, **kwargs: None) @@ -64,7 +66,7 @@ def test_partial_refund_disbursement(session, monkeypatch): refund_partial_link = EjvLink.find_ejv_link_by_link_id(refund_partial.id) assert refund_partial_link is None - day_after_time_delay = datetime.today() + timedelta(days=( + day_after_time_delay = datetime.now(tz=timezone.utc) + timedelta(days=( current_app.config.get('DISBURSEMENT_DELAY_IN_DAYS') + 1)) with freeze_time(day_after_time_delay): diff --git a/jobs/payment-jobs/tests/jobs/test_generate_statements.py b/jobs/payment-jobs/tests/jobs/test_generate_statements.py deleted file mode 100644 index 32504f94e..000000000 --- a/jobs/payment-jobs/tests/jobs/test_generate_statements.py +++ /dev/null @@ -1,280 +0,0 @@ -# Copyright © 2019 Province of British Columbia -# -# 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. - -"""Tests to assure the UpdateStalePayment. - -Test-Suite to ensure that the UpdateStalePayment is working as expected. -""" -from datetime import datetime, timedelta - -import pytz -from freezegun import freeze_time -from pay_api.models import Statement, StatementInvoices -from pay_api.services import Statement as StatementService -from pay_api.utils.enums import InvoiceStatus, PaymentMethod, StatementFrequency -from pay_api.utils.util import get_previous_day - -from tasks.statement_task import StatementTask - -from .factory import ( - factory_create_account, factory_invoice, factory_invoice_reference, factory_payment, - factory_premium_payment_account, factory_statement_settings) - - -@freeze_time('2023-01-02 12:00:00T08:00:00') -def test_statements(session): - """Test daily statement generation works. - - Steps: - 1) Create a payment for yesterday - 2) Mark the account settings as DAILY settlement starting yesterday - 3) Generate statement and assert that the statement contains payment records - """ - previous_day = localize_date(get_previous_day(datetime.utcnow())) - - bcol_account = factory_premium_payment_account() - invoice = factory_invoice(payment_account=bcol_account, created_on=previous_day) - inv_ref = factory_invoice_reference(invoice_id=invoice.id) - factory_payment(payment_date=previous_day, invoice_number=inv_ref.invoice_number) - - factory_statement_settings( - pay_account_id=bcol_account.id, - from_date=previous_day, - frequency='DAILY' - ) - factory_statement_settings( - pay_account_id=bcol_account.id, - from_date=get_previous_day(previous_day), - frequency='DAILY' - ) - factory_statement_settings( - pay_account_id=bcol_account.id, - from_date=datetime.utcnow(), - frequency='DAILY' - ) - StatementTask.generate_statements() - - statements = StatementService.get_account_statements(auth_account_id=bcol_account.auth_account_id, - page=1, limit=100) - assert statements is not None - first_statement_id = statements[0][0].id - invoices = StatementInvoices.find_all_invoices_for_statement(first_statement_id) - assert invoices is not None - assert invoices[0].invoice_id == invoice.id - - # Test date override. - # Override computes for the target date, not the previous date like above. - StatementTask.generate_statements([(datetime.utcnow() - timedelta(days=1)).strftime('%Y-%m-%d')]) - - statements = StatementService.get_account_statements(auth_account_id=bcol_account.auth_account_id, - page=1, limit=100) - assert statements is not None - invoices = StatementInvoices.find_all_invoices_for_statement(statements[0][0].id) - assert invoices is not None - assert invoices[0].invoice_id == invoice.id - - # Check to see if the old statement / invoices were cleaned up. - assert Statement.find_by_id(first_statement_id) is None - assert StatementInvoices.find_all_invoices_for_statement(first_statement_id) == [] - - -def test_statements_for_empty_results(session): - """Test daily statement generation works. - - Steps: - 1) Create a payment for day before yesterday - 2) Mark the account settings as DAILY settlement starting yesterday - 3) Generate statement and assert that the statement does not contains payment records - """ - day_before_yday = get_previous_day(datetime.now()) - timedelta(days=1) - bcol_account = factory_premium_payment_account() - invoice = factory_invoice(payment_account=bcol_account, created_on=day_before_yday) - inv_ref = factory_invoice_reference(invoice_id=invoice.id) - factory_statement_settings( - pay_account_id=bcol_account.id, - from_date=day_before_yday, - frequency='DAILY' - ) - factory_payment(payment_date=day_before_yday, invoice_number=inv_ref.invoice_number) - - StatementTask.generate_statements() - - statements = StatementService.get_account_statements(auth_account_id=bcol_account.auth_account_id, - page=1, limit=100) - assert statements is not None - invoices = StatementInvoices.find_all_invoices_for_statement(statements[0][0].id) - assert len(invoices) == 0 - - -def test_bcol_weekly_to_eft_statement(session): - """Test transition to EFT statement with an existing weekly interim statement.""" - # Account set up - account_create_date = datetime(2023, 10, 1, 12, 0) - with freeze_time(account_create_date): - account = factory_create_account(auth_account_id='1', payment_method_code=PaymentMethod.EFT.value) - assert account is not None - - # Setup previous payment method interim statement data - invoice_create_date = localize_date(datetime(2023, 10, 9, 12, 0)) - weekly_invoice = factory_invoice(payment_account=account, created_on=invoice_create_date, - payment_method_code=PaymentMethod.DRAWDOWN.value, - status_code=InvoiceStatus.CREATED.value, - total=50) - - assert weekly_invoice is not None - - statement_from_date = localize_date(datetime(2023, 10, 8, 12, 0)) - statement_to_date = localize_date(datetime(2023, 10, 12, 12, 0)) - - # Set up initial statement settings - factory_statement_settings( - pay_account_id=account.id, - from_date=statement_from_date, - to_date=statement_to_date, - frequency=StatementFrequency.WEEKLY.value - ).save() - - generate_date = localize_date(datetime(2023, 10, 12, 12, 0)) - with freeze_time(generate_date): - weekly_statement = StatementService.generate_interim_statement(auth_account_id=account.auth_account_id, - new_frequency=StatementFrequency.MONTHLY.value) - - # Validate weekly interim invoice is correct - weekly_invoices = StatementInvoices.find_all_invoices_for_statement(weekly_statement.id) - assert weekly_invoices is not None - assert len(weekly_invoices) == 1 - assert weekly_invoices[0].invoice_id == weekly_invoice.id - - # Generate monthly statement using the 1st of next month - generate_date = localize_date(datetime(2023, 11, 1, 12, 0)) - with freeze_time(generate_date): - StatementTask.generate_statements() - - # Validate there are no invoices associated with this statement - statements = StatementService.get_account_statements(auth_account_id=account.auth_account_id, page=1, limit=100) - assert statements is not None - assert len(statements[0]) == 2 - first_statement_id = statements[0][0].id - monthly_invoices = StatementInvoices.find_all_invoices_for_statement(first_statement_id) - assert len(monthly_invoices) == 0 - - # Set up and EFT invoice - # Using the same invoice create date as the weekly to test invoices on the same day with different payment methods - monthly_invoice = factory_invoice(payment_account=account, created_on=invoice_create_date, - payment_method_code=PaymentMethod.EFT.value, - status_code=InvoiceStatus.CREATED.value, - total=50) - - assert monthly_invoice is not None - - # Regenerate monthly statement using date override - it will clean up the previous empty monthly statement first - StatementTask.generate_statements([(generate_date - timedelta(days=1)).strftime('%Y-%m-%d')]) - - statements = StatementService.get_account_statements(auth_account_id=account.auth_account_id, page=1, limit=100) - - assert statements is not None - assert len(statements[0]) == 2 # Should still be 2 statements as the previous empty one should be removed - first_statement_id = statements[0][0].id - monthly_invoices = StatementInvoices.find_all_invoices_for_statement(first_statement_id) - assert monthly_invoices is not None - assert len(monthly_invoices) == 1 - assert monthly_invoices[0].invoice_id == monthly_invoice.id - - -def test_bcol_monthly_to_eft_statement(session): - """Test transition to EFT statement with an existing monthly interim statement.""" - # Account set up - account_create_date = datetime(2023, 10, 1, 12, 0) - with freeze_time(account_create_date): - account = factory_create_account(auth_account_id='1', payment_method_code=PaymentMethod.EFT.value) - assert account is not None - - # Setup previous payment method interim statement data - invoice_create_date = localize_date(datetime(2023, 10, 9, 12, 0)) - bcol_invoice = factory_invoice(payment_account=account, created_on=invoice_create_date, - payment_method_code=PaymentMethod.DRAWDOWN.value, - status_code=InvoiceStatus.CREATED.value, - total=50) - - assert bcol_invoice is not None - - statement_from_date = localize_date(datetime(2023, 10, 1, 12, 0)) - statement_to_date = localize_date(datetime(2023, 10, 30, 12, 0)) - - # Set up initial statement settings - factory_statement_settings( - pay_account_id=account.id, - from_date=statement_from_date, - to_date=statement_to_date, - frequency=StatementFrequency.MONTHLY.value - ).save() - - generate_date = localize_date(datetime(2023, 10, 12, 12, 0)) - with freeze_time(generate_date): - bcol_monthly_statement = StatementService\ - .generate_interim_statement(auth_account_id=account.auth_account_id, - new_frequency=StatementFrequency.MONTHLY.value) - - # Validate bcol monthly interim invoice is correct - bcol_invoices = StatementInvoices.find_all_invoices_for_statement(bcol_monthly_statement.id) - assert bcol_invoices is not None - assert len(bcol_invoices) == 1 - assert bcol_invoices[0].invoice_id == bcol_invoice.id - - # Generate monthly statement using the 1st of next month - generate_date = localize_date(datetime(2023, 11, 1, 12, 0)) - with freeze_time(generate_date): - StatementTask.generate_statements() - - # Validate there are no invoices associated with this statement - statements = StatementService.get_account_statements(auth_account_id=account.auth_account_id, page=1, limit=100) - assert statements is not None - assert len(statements[0]) == 2 - first_statement_id = statements[0][0].id - monthly_invoices = StatementInvoices.find_all_invoices_for_statement(first_statement_id) - assert len(monthly_invoices) == 0 - - # Set up and EFT invoice - # Using the same invoice create date as the weekly to test invoices on the same day with different payment methods - monthly_invoice = factory_invoice(payment_account=account, created_on=invoice_create_date, - payment_method_code=PaymentMethod.EFT.value, - status_code=InvoiceStatus.CREATED.value, - total=50) - - assert monthly_invoice is not None - - # Regenerate monthly statement using date override - it will clean up the previous empty monthly statement first - StatementTask.generate_statements([(generate_date - timedelta(days=1)).strftime('%Y-%m-%d')]) - - statements = StatementService.get_account_statements(auth_account_id=account.auth_account_id, page=1, limit=100) - - assert statements is not None - assert len(statements[0]) == 2 # Should still be 2 statements as the previous empty one should be removed - first_statement_id = statements[0][0].id - monthly_invoices = StatementInvoices.find_all_invoices_for_statement(first_statement_id) - assert monthly_invoices is not None - assert len(monthly_invoices) == 1 - assert monthly_invoices[0].invoice_id == monthly_invoice.id - - # Validate bcol monthly interim invoice is correct - bcol_invoices = StatementInvoices.find_all_invoices_for_statement(bcol_monthly_statement.id) - assert bcol_invoices is not None - assert len(bcol_invoices) == 1 - assert bcol_invoices[0].invoice_id == bcol_invoice.id - - -def localize_date(date: datetime): - """Localize date object by adding timezone information.""" - pst = pytz.timezone('America/Vancouver') - return pst.localize(date) diff --git a/jobs/payment-jobs/tests/jobs/test_statement_due_task.py b/jobs/payment-jobs/tests/jobs/test_statement_due_task.py index 4176cd00e..affa356fc 100644 --- a/jobs/payment-jobs/tests/jobs/test_statement_due_task.py +++ b/jobs/payment-jobs/tests/jobs/test_statement_due_task.py @@ -17,7 +17,7 @@ Test-Suite to ensure that the UnpaidStatementNotifyTask is working as expected. """ import decimal -from datetime import datetime, timedelta +from datetime import datetime from dateutil.relativedelta import relativedelta from unittest.mock import patch @@ -25,10 +25,11 @@ from faker import Faker from flask import Flask from freezegun import freeze_time +from pay_api.models import NonSufficientFunds as NonSufficientFundsModel from pay_api.models import StatementInvoices as StatementInvoicesModel from pay_api.services import Statement as StatementService from pay_api.utils.enums import InvoiceStatus, PaymentMethod, StatementFrequency -from pay_api.utils.util import current_local_time, get_first_and_last_dates_of_month, get_previous_month_and_year +from pay_api.utils.util import current_local_time import config from tasks.statement_task import StatementTask @@ -61,7 +62,7 @@ def create_test_data(payment_method_code: str, payment_date: datetime, """Create seed data for tests.""" account = factory_create_account(auth_account_id='1', payment_method_code=payment_method_code) invoice = factory_invoice(payment_account=account, created_on=payment_date, - payment_method_code=payment_method_code, status_code=InvoiceStatus.CREATED.value, + payment_method_code=payment_method_code, status_code=InvoiceStatus.APPROVED.value, total=invoice_total) inv_ref = factory_invoice_reference(invoice_id=invoice.id) statement_recipient = factory_statement_recipient(auth_user_id=account.auth_account_id, @@ -79,24 +80,29 @@ def create_test_data(payment_method_code: str, payment_date: datetime, return account, invoice, inv_ref, statement_recipient, statement_settings -def test_send_unpaid_statement_notification(setup, session): +# 1. EFT Invoice created between or on January 1st <-> January 31st +# 2. Statement Day February 1st +# 3. 7 day reminder Feb 21th (due date - 7) +# 4. Final reminder Feb 28th (due date client should be told to pay by this time) +# 5. Overdue Date and account locked March 15th +@pytest.mark.parametrize('test_name, action_on, action', [ + ('reminder', datetime(2023, 2, 21, 8), StatementNotificationAction.REMINDER), + ('due', datetime(2023, 2, 28, 8), StatementNotificationAction.DUE), + ('overdue', datetime(2023, 3, 15, 8), StatementNotificationAction.OVERDUE) +]) +def test_send_unpaid_statement_notification(setup, session, test_name, action_on, action): """Assert payment reminder event is being sent.""" - last_month, last_year = get_previous_month_and_year() - previous_month_year = datetime(last_year, last_month, 5) - - account, invoice, inv_ref, \ - statement_recipient, statement_settings = create_test_data(PaymentMethod.EFT.value, - previous_month_year, - StatementFrequency.MONTHLY.value, - 351.50) + account, invoice, _, \ + statement_recipient, _ = create_test_data(PaymentMethod.EFT.value, + datetime(2023, 1, 1, 8), # Hour 0 doesnt work for CI + StatementFrequency.MONTHLY.value, + 351.50) assert invoice.payment_method_code == PaymentMethod.EFT.value + assert invoice.overdue_date assert account.payment_method == PaymentMethod.EFT.value - now = current_local_time().replace(hour=1) - _, last_day = get_first_and_last_dates_of_month(now.month, now.year) - - # Generate statement for previous month - freeze time to the 1st of the current month - with freeze_time(current_local_time().replace(day=1, hour=1)): + # Generate statements runs at 8:01 UTC, currently set to 7:01 UTC, should be moved. + with freeze_time(datetime(2023, 2, 1, 8, 0, 1)): StatementTask.generate_statements() statements = StatementService.get_account_statements(auth_account_id=account.auth_account_id, page=1, limit=100) @@ -110,32 +116,25 @@ def test_send_unpaid_statement_notification(setup, session): summary = StatementService.get_summary(account.auth_account_id, statements[0][0].id) total_amount_owing = summary['total_due'] - with patch('tasks.statement_due_task.publish_payment_notification') as mock_mailer: - with freeze_time(last_day): - StatementDueTask.process_unpaid_statements() - mock_mailer.assert_called_with(StatementNotificationInfo(auth_account_id=account.auth_account_id, - statement=statements[0][0], - action=StatementNotificationAction.DUE, - due_date=last_day.date(), - emails=statement_recipient.email, - total_amount_owing=total_amount_owing)) - - with freeze_time(last_day - timedelta(days=7)): - StatementDueTask.process_unpaid_statements() - mock_mailer.assert_called_with(StatementNotificationInfo(auth_account_id=account.auth_account_id, - statement=statements[0][0], - action=StatementNotificationAction.REMINDER, - due_date=last_day.date(), - emails=statement_recipient.email, - total_amount_owing=total_amount_owing)) - with freeze_time(last_day + timedelta(days=7)): - StatementDueTask.process_unpaid_statements() - mock_mailer.assert_called_with(StatementNotificationInfo(auth_account_id=account.auth_account_id, - statement=statements[0][0], - action=StatementNotificationAction.OVERDUE, - due_date=last_day.date(), - emails=statement_recipient.email, - total_amount_owing=total_amount_owing)) + with patch('utils.auth_event.AuthEvent.publish_lock_account_event') as mock_auth_event: + with patch('tasks.statement_due_task.publish_payment_notification') as mock_mailer: + with freeze_time(action_on): + # Statement due task looks at the month before. + StatementDueTask.process_unpaid_statements(statement_date_override=datetime(2023, 2, 1, 0)) + if action == StatementNotificationAction.OVERDUE: + mock_auth_event.assert_called() + assert statements[0][0].overdue_notification_date + assert NonSufficientFundsModel.find_by_invoice_id(invoice.id) + assert account.has_overdue_invoices + else: + due_date = statements[0][0].to_date + relativedelta(months=1) + mock_mailer.assert_called_with(StatementNotificationInfo(auth_account_id=account.auth_account_id, + statement=statements[0][0], + action=action, + due_date=due_date, + emails=statement_recipient.email, + total_amount_owing=total_amount_owing, + short_name_links_count=0)) def test_unpaid_statement_notification_not_sent(setup, session): @@ -150,30 +149,23 @@ def test_unpaid_statement_notification_not_sent(setup, session): def test_overdue_invoices_updated(setup, session): """Assert invoices are transitioned to overdue status.""" - invoice_date = current_local_time() + relativedelta(months=-1, day=5) - - # Freeze time to the previous month so the overdue date is set properly and in the past for this test - with freeze_time(invoice_date): - account, invoice, inv_ref, \ - statement_recipient, statement_settings = create_test_data(PaymentMethod.EFT.value, - invoice_date, - StatementFrequency.MONTHLY.value, - 351.50) - - # Freeze time to the current month so the overdue date is in the future for this test - with freeze_time(current_local_time().replace(day=5)): - invoice2 = factory_invoice(payment_account=account, created_on=current_local_time().date(), - payment_method_code=PaymentMethod.EFT.value, status_code=InvoiceStatus.CREATED.value, - total=10.50) - + invoice_date = current_local_time() - relativedelta(months=2, days=15) + account, invoice, _, \ + _, _ = create_test_data(PaymentMethod.EFT.value, + invoice_date, + StatementFrequency.MONTHLY.value, + 351.50) assert invoice.payment_method_code == PaymentMethod.EFT.value - assert invoice.invoice_status_code == InvoiceStatus.CREATED.value + assert invoice.invoice_status_code == InvoiceStatus.APPROVED.value + + invoice2 = factory_invoice(payment_account=account, created_on=current_local_time().date() + relativedelta(hours=1), + payment_method_code=PaymentMethod.EFT.value, status_code=InvoiceStatus.APPROVED.value, + total=10.50) + assert invoice2.payment_method_code == PaymentMethod.EFT.value - assert invoice2.invoice_status_code == InvoiceStatus.CREATED.value + assert invoice2.invoice_status_code == InvoiceStatus.APPROVED.value assert account.payment_method == PaymentMethod.EFT.value - # Freeze time to 1st of the month - should trigger overdue status update for previous month invoices - with freeze_time(current_local_time().replace(day=1)): - StatementDueTask.process_unpaid_statements() - assert invoice.invoice_status_code == InvoiceStatus.OVERDUE.value - assert invoice2.invoice_status_code == InvoiceStatus.CREATED.value + StatementDueTask.process_unpaid_statements(auth_account_override=account.auth_account_id) + assert invoice.invoice_status_code == InvoiceStatus.OVERDUE.value + assert invoice2.invoice_status_code == InvoiceStatus.APPROVED.value diff --git a/jobs/payment-jobs/tests/jobs/test_statement_notification_task.py b/jobs/payment-jobs/tests/jobs/test_statement_notification_task.py index df3c6dc5e..7837870b9 100644 --- a/jobs/payment-jobs/tests/jobs/test_statement_notification_task.py +++ b/jobs/payment-jobs/tests/jobs/test_statement_notification_task.py @@ -17,7 +17,7 @@ Test-Suite to ensure that the StatementNotificationTask is working as expected. """ import decimal -from datetime import datetime +from datetime import datetime, timezone from unittest.mock import ANY, patch import pytest @@ -105,7 +105,7 @@ def test_send_monthly_notifications(setup, session, payment_method_code): # pyl assert account.payment_method == payment_method_code # Generate statement for previous month - freeze time to the 1st of the current month - with freeze_time(datetime.now().replace(day=1)): + with freeze_time(datetime.now(tz=timezone.utc).replace(day=1, hour=8)): StatementTask.generate_statements() # Assert statements and invoice was created @@ -156,7 +156,7 @@ def test_send_monthly_notifications_failed(setup, session, payment_method_code): assert account.payment_method == payment_method_code # Generate statement for previous month - freeze time to the 1st of the current month - with freeze_time(datetime.now().replace(day=1)): + with freeze_time(datetime.now(tz=timezone.utc).replace(day=1, hour=8)): StatementTask.generate_statements() # Assert statements and invoice was created @@ -198,7 +198,7 @@ def test_send_eft_notifications(setup, session): # pylint: disable=unused-argum assert account.payment_method == PaymentMethod.EFT.value # Generate statement for previous month - freeze time to the 1st of the current month - with freeze_time(datetime.now().replace(day=1)): + with freeze_time(datetime.now(tz=timezone.utc).replace(day=1, hour=8)): StatementTask.generate_statements() # Assert statements and invoice was created @@ -239,7 +239,7 @@ def test_send_eft_notifications_failure(setup, session): # pylint: disable=unus assert account.payment_method == PaymentMethod.EFT.value # Generate statement for previous month - freeze time to the 1st of the current month - with freeze_time(datetime.now().replace(day=1)): + with freeze_time(datetime.now(tz=timezone.utc).replace(day=1, hour=8)): StatementTask.generate_statements() # Assert statements and invoice was created @@ -281,7 +281,7 @@ def test_send_eft_notifications_ff_disabled(setup, session): # pylint: disable= assert account.payment_method == PaymentMethod.EFT.value # Generate statement for previous month - freeze time to the 1st of the current month - with freeze_time(datetime.now().replace(day=1)): + with freeze_time(datetime.now(tz=timezone.utc).replace(day=1, hour=8)): StatementTask.generate_statements() # Assert statements and invoice was created diff --git a/jobs/payment-jobs/tests/jobs/test_statements_task.py b/jobs/payment-jobs/tests/jobs/test_statements_task.py new file mode 100644 index 000000000..f4b94a0aa --- /dev/null +++ b/jobs/payment-jobs/tests/jobs/test_statements_task.py @@ -0,0 +1,497 @@ +# Copyright © 2019 Province of British Columbia +# +# 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. + +"""Tests to assure the UpdateStalePayment. + +Test-Suite to ensure that the UpdateStalePayment is working as expected. +""" +from datetime import datetime, timedelta, timezone + +import pytz +import pytest +from freezegun import freeze_time +from pay_api.models import Invoice as InvoiceModel +from pay_api.models import Statement, StatementInvoices, StatementSettings, db +from pay_api.services import Statement as StatementService +from pay_api.services import StatementSettings as StatementSettingsService +from pay_api.services.payment_account import PaymentAccount as PaymentAccountService +from pay_api.utils.enums import InvoiceStatus, PaymentMethod, StatementFrequency +from pay_api.utils.util import get_previous_day +from sqlalchemy import insert + +from tasks.statement_task import StatementTask + +from .factory import ( + factory_create_account, factory_eft_account_payload, factory_invoice, factory_invoice_reference, + factory_pad_account_payload, factory_payment, factory_premium_payment_account, factory_statement_settings) + + +@freeze_time('2023-01-02 12:00:00T08:00:00') +def test_statements(session): + """Test daily statement generation works. + + Steps: + 1) Create a payment for yesterday + 2) Mark the account settings as DAILY settlement starting yesterday + 3) Generate statement and assert that the statement contains payment records + """ + previous_day = localize_date(get_previous_day(datetime.utcnow())) + + bcol_account = factory_premium_payment_account() + invoice = factory_invoice(payment_account=bcol_account, created_on=previous_day) + inv_ref = factory_invoice_reference(invoice_id=invoice.id) + factory_payment(payment_date=previous_day, invoice_number=inv_ref.invoice_number) + + factory_statement_settings( + pay_account_id=bcol_account.id, + from_date=previous_day, + frequency='DAILY' + ) + factory_statement_settings( + pay_account_id=bcol_account.id, + from_date=get_previous_day(previous_day), + frequency='DAILY' + ) + factory_statement_settings( + pay_account_id=bcol_account.id, + from_date=datetime.utcnow(), + frequency='DAILY' + ) + StatementTask.generate_statements() + + statements = StatementService.get_account_statements(auth_account_id=bcol_account.auth_account_id, + page=1, limit=100) + assert statements is not None + first_statement_id = statements[0][0].id + invoices = StatementInvoices.find_all_invoices_for_statement(first_statement_id) + assert invoices is not None + assert invoices[0].invoice_id == invoice.id + + # Test date override. + # Override computes for the target date, not the previous date like above. + StatementTask.generate_statements([(datetime.utcnow() - timedelta(days=1)).strftime('%Y-%m-%d')]) + + statements = StatementService.get_account_statements(auth_account_id=bcol_account.auth_account_id, + page=1, limit=100) + assert statements is not None + invoices = StatementInvoices.find_all_invoices_for_statement(statements[0][0].id) + assert invoices is not None + assert invoices[0].invoice_id == invoice.id + + # Check to see if the old statement was reused and invoices were cleaned up. + assert Statement.find_by_id(first_statement_id) + assert first_statement_id == statements[0][0].id + assert len(StatementInvoices.find_all_invoices_for_statement(first_statement_id)) == 2 + + +def test_statements_for_empty_results(session): + """Test daily statement generation works. + + Steps: + 1) Create a payment for day before yesterday + 2) Mark the account settings as DAILY settlement starting yesterday + 3) Generate statement and assert that the statement does not contains payment records + """ + day_before_yday = get_previous_day(datetime.now(tz=timezone.utc)) - timedelta(days=1) + bcol_account = factory_premium_payment_account() + invoice = factory_invoice(payment_account=bcol_account, created_on=day_before_yday) + inv_ref = factory_invoice_reference(invoice_id=invoice.id) + factory_statement_settings( + pay_account_id=bcol_account.id, + from_date=day_before_yday, + frequency='DAILY' + ) + factory_payment(payment_date=day_before_yday, invoice_number=inv_ref.invoice_number) + + StatementTask.generate_statements() + + statements = StatementService.get_account_statements(auth_account_id=bcol_account.auth_account_id, + page=1, limit=100) + assert statements is not None + invoices = StatementInvoices.find_all_invoices_for_statement(statements[0][0].id) + assert len(invoices) == 0 + + +def test_bcol_weekly_to_eft_statement(session): + """Test transition to EFT statement with an existing weekly interim statement.""" + # Account set up + account_create_date = datetime(2023, 10, 1, 12, 0) + with freeze_time(account_create_date): + account = factory_create_account(auth_account_id='1', payment_method_code=PaymentMethod.EFT.value) + assert account is not None + + # Setup previous payment method interim statement data + invoice_create_date = localize_date(datetime(2023, 10, 9, 12, 0)) + weekly_invoice = factory_invoice(payment_account=account, created_on=invoice_create_date, + payment_method_code=PaymentMethod.DRAWDOWN.value, + status_code=InvoiceStatus.APPROVED.value, + total=50) + + assert weekly_invoice is not None + + statement_from_date = localize_date(datetime(2023, 10, 8, 12, 0)) + statement_to_date = localize_date(datetime(2023, 10, 12, 12, 0)) + + # Set up initial statement settings + factory_statement_settings( + pay_account_id=account.id, + from_date=statement_from_date, + to_date=statement_to_date, + frequency=StatementFrequency.WEEKLY.value + ).save() + + generate_date = localize_date(datetime(2023, 10, 12, 12, 0)) + with freeze_time(generate_date): + weekly_statement = StatementService.generate_interim_statement(auth_account_id=account.auth_account_id, + new_frequency=StatementFrequency.MONTHLY.value) + + # Validate weekly interim invoice is correct + weekly_invoices = StatementInvoices.find_all_invoices_for_statement(weekly_statement.id) + assert weekly_invoices is not None + assert len(weekly_invoices) == 1 + assert weekly_invoices[0].invoice_id == weekly_invoice.id + + # Generate monthly statement using the 1st of next month + generate_date = localize_date(datetime(2023, 11, 1, 12, 0)) + with freeze_time(generate_date): + StatementTask.generate_statements() + + # Validate there are no invoices associated with this statement + statements = StatementService.get_account_statements(auth_account_id=account.auth_account_id, page=1, limit=100) + assert statements is not None + assert len(statements[0]) == 2 + first_statement_id = statements[0][0].id + monthly_invoices = StatementInvoices.find_all_invoices_for_statement(first_statement_id) + assert len(monthly_invoices) == 0 + + # Set up and EFT invoice + # Using the same invoice create date as the weekly to test invoices on the same day with different payment methods + monthly_invoice = factory_invoice(payment_account=account, created_on=invoice_create_date, + payment_method_code=PaymentMethod.EFT.value, + status_code=InvoiceStatus.APPROVED.value, + total=50) + + assert monthly_invoice is not None + + # Regenerate monthly statement using date override - it will clean up the previous empty monthly statement first + StatementTask.generate_statements([(generate_date - timedelta(days=1)).strftime('%Y-%m-%d')]) + + statements = StatementService.get_account_statements(auth_account_id=account.auth_account_id, page=1, limit=100) + + assert statements is not None + assert len(statements[0]) == 2 # Should still be 2 statements as the previous empty one should be removed + first_statement_id = statements[0][0].id + monthly_invoices = StatementInvoices.find_all_invoices_for_statement(first_statement_id) + assert monthly_invoices is not None + assert len(monthly_invoices) == 1 + assert monthly_invoices[0].invoice_id == monthly_invoice.id + + +def test_bcol_monthly_to_eft_statement(session): + """Test transition to EFT statement with an existing monthly interim statement.""" + # Account set up + account_create_date = datetime(2023, 10, 1, 12, 0) + with freeze_time(account_create_date): + account = factory_create_account(auth_account_id='1', payment_method_code=PaymentMethod.EFT.value) + assert account is not None + + # Setup previous payment method interim statement data + invoice_create_date = localize_date(datetime(2023, 10, 9, 12, 0)) + bcol_invoice = factory_invoice(payment_account=account, created_on=invoice_create_date, + payment_method_code=PaymentMethod.DRAWDOWN.value, + status_code=InvoiceStatus.APPROVED.value, + total=50) + + assert bcol_invoice is not None + direct_pay_invoice = factory_invoice(payment_account=account, created_on=invoice_create_date, + payment_method_code=PaymentMethod.DIRECT_PAY.value, + status_code=InvoiceStatus.APPROVED.value, + total=50) + assert direct_pay_invoice + + statement_from_date = localize_date(datetime(2023, 10, 1, 12, 0)) + statement_to_date = localize_date(datetime(2023, 10, 30, 12, 0)) + + # Set up initial statement settings + factory_statement_settings( + pay_account_id=account.id, + from_date=statement_from_date, + to_date=statement_to_date, + frequency=StatementFrequency.MONTHLY.value + ).save() + + generate_date = localize_date(datetime(2023, 10, 12, 12, 0)) + with freeze_time(generate_date): + bcol_monthly_statement = StatementService\ + .generate_interim_statement(auth_account_id=account.auth_account_id, + new_frequency=StatementFrequency.MONTHLY.value) + account.payment_method_code = PaymentMethod.EFT.value + account.save() + + # Validate bcol monthly interim invoice is correct + invoices = StatementInvoices.find_all_invoices_for_statement(bcol_monthly_statement.id) + assert invoices is not None + assert len(invoices) == 2 + assert invoices[0].invoice_id == bcol_invoice.id + assert invoices[1].invoice_id == direct_pay_invoice.id + + # Generate monthly statement using the 1st of next month + generate_date = localize_date(datetime(2023, 11, 1, 12, 0)) + with freeze_time(generate_date): + StatementTask.generate_statements() + + # Validate there are no invoices associated with this statement + statements = StatementService.get_account_statements(auth_account_id=account.auth_account_id, page=1, limit=100) + assert statements is not None + assert len(statements[0]) == 2 + first_statement_id = statements[0][0].id + # Test invoices existing and payment_account.payment_method_code fallback. + assert statements[0][0].payment_methods == PaymentMethod.EFT.value + assert statements[0][1].payment_methods in [f'{PaymentMethod.DIRECT_PAY.value},{PaymentMethod.DRAWDOWN.value}', + f'{PaymentMethod.DRAWDOWN.value},{PaymentMethod.DIRECT_PAY.value}'] + monthly_invoices = StatementInvoices.find_all_invoices_for_statement(first_statement_id) + assert len(monthly_invoices) == 0 + + # Set up and EFT invoice + # Using the same invoice create date as the weekly to test invoices on the same day with different payment methods + monthly_invoice = factory_invoice(payment_account=account, created_on=invoice_create_date, + payment_method_code=PaymentMethod.EFT.value, + status_code=InvoiceStatus.APPROVED.value, + total=50) + + assert monthly_invoice is not None + # This should get ignored. + monthly_invoice_2 = factory_invoice(payment_account=account, created_on=invoice_create_date, + payment_method_code=PaymentMethod.DIRECT_PAY.value, + status_code=InvoiceStatus.APPROVED.value, + total=50) + assert monthly_invoice_2 + + # Regenerate monthly statement using date override - it will clean up the previous empty monthly statement first + StatementTask.generate_statements([(generate_date - timedelta(days=1)).strftime('%Y-%m-%d')]) + + statements = StatementService.get_account_statements(auth_account_id=account.auth_account_id, page=1, limit=100) + + assert statements is not None + assert len(statements[0]) == 2 # Should still be 2 statements as the previous empty one should be removed + first_statement_id = statements[0][0].id + monthly_invoices = StatementInvoices.find_all_invoices_for_statement(first_statement_id) + assert monthly_invoices is not None + assert len(monthly_invoices) == 1 + assert monthly_invoices[0].invoice_id == monthly_invoice.id + # This should be EFT only, because there's a filter in the jobs that looks only for EFT invoices if + # payment_account is set to EFT. + assert statements[0][0].payment_methods == f'{PaymentMethod.EFT.value}' + + # Validate bcol monthly interim invoice is correct + bcol_invoices = StatementInvoices.find_all_invoices_for_statement(bcol_monthly_statement.id) + assert bcol_invoices is not None + assert len(bcol_invoices) == 2 + assert bcol_invoices[0].invoice_id == bcol_invoice.id + assert bcol_invoices[1].invoice_id == direct_pay_invoice.id + + +def test_many_statements(): + """Ensure many statements work over 65535 statements.""" + account = factory_create_account(auth_account_id='1') + factory_statement_settings( + pay_account_id=account.id, + from_date=datetime(2024, 1, 1, 8), + to_date=datetime(2024, 1, 4, 8), + frequency=StatementFrequency.DAILY.value + ).save() + invoice = factory_invoice(account) + statement_list = [] + for _ in range(0, 70000): + statement_list.append({'created_on': '2024-01-01', + 'from_date': '2024-01-01 08:00:00', + 'to_date': '2024-01-04 08:00:00', + 'payment_account_id': f'{account.id}', + 'frequency': StatementFrequency.DAILY.value + }) + db.session.execute(insert(Statement), statement_list) + + statement = db.session.query(Statement).first() + statement_invoices_list = [] + for _ in range(0, 70000): + statement_invoices_list.append({ + 'statement_id': statement.id, + 'invoice_id': invoice.id + }) + db.session.execute(insert(StatementInvoices), statement_invoices_list) + StatementTask.generate_statements([datetime(2024, 1, 1, 8).strftime('%Y-%m-%d')]) + assert True + + +@pytest.mark.parametrize('test_name', [('interim_overlap'), ('non_interim'), ('pad_to_eft'), ('eft_to_pad')]) +def test_gap_statements(session, test_name, admin_users_mock): + """Ensure gap statements are generated for weekly to monthly.""" + account_create_date = datetime(2024, 1, 1, 8) + if test_name == 'interim_overlap': + account_create_date = datetime(2024, 8, 18, 15, 0) + account = None + invoice_ids = [] + with freeze_time(account_create_date): + match test_name: + case 'eft_to_pad': + account = factory_create_account(auth_account_id='1', payment_method_code=PaymentMethod.EFT.value) + from_date = (localize_date(account_create_date)).date() + case 'interim_overlap': + account = factory_create_account(auth_account_id='1', payment_method_code=PaymentMethod.PAD.value) + from_date = (localize_date(datetime(2024, 8, 22, 15, 0))).date() + case _: + account = factory_create_account(auth_account_id='1', payment_method_code=PaymentMethod.PAD.value) + from_date = (localize_date(account_create_date)).date() + StatementInvoices.query.delete() + Statement.query.delete() + StatementSettings.query.delete() + InvoiceModel.query.delete() + frequency = StatementFrequency.MONTHLY.value if test_name == 'eft_to_pad' else StatementFrequency.WEEKLY.value + factory_statement_settings(pay_account_id=account.id, + frequency=frequency, + from_date=from_date + ).save() + + match test_name: + case 'interim_overlap': + inv = factory_invoice(payment_account=account, + payment_method_code=PaymentMethod.PAD.value, + status_code=InvoiceStatus.PAID.value, + total=31.50, + created_on=datetime(2024, 8, 19, 15, 15)).save() + invoice_ids.append(inv.id) + case 'non_interim': + for i in range(0, 31): + inv = factory_invoice(payment_account=account, + payment_method_code=PaymentMethod.PAD.value, + status_code=InvoiceStatus.PAID.value, + total=50, + created_on=account_create_date + timedelta(i)) \ + .save() + invoice_ids.append(inv.id) + case 'pad_to_eft': + for i in range(0, 28): + inv = factory_invoice(payment_account=account, + payment_method_code=PaymentMethod.PAD.value, + status_code=InvoiceStatus.PAID.value, + total=50, + created_on=account_create_date + timedelta(i)) \ + .save() + invoice_ids.append(inv.id) + # Overlap an EFT invoice and PAD on the same day. + for i in range(28, 31): + inv = factory_invoice(payment_account=account, + payment_method_code=PaymentMethod.EFT.value, + status_code=InvoiceStatus.PAID.value, + total=50, + created_on=account_create_date + timedelta(i)) \ + .save() + invoice_ids.append(inv.id) + case 'eft_to_pad': + for i in range(0, 28): + inv = factory_invoice(payment_account=account, + payment_method_code=PaymentMethod.EFT.value, + status_code=InvoiceStatus.PAID.value, + total=50, + created_on=account_create_date + timedelta(i)) \ + .save() + invoice_ids.append(inv.id) + # Overlap an EFT invoice and PAD on the same day. + for i in range(28, 31): + inv = factory_invoice(payment_account=account, + payment_method_code=PaymentMethod.PAD.value, + status_code=InvoiceStatus.PAID.value, + total=50, + created_on=account_create_date + timedelta(i)) \ + .save() + invoice_ids.append(inv.id) + + match test_name: + case 'interim_overlap': + with freeze_time(localize_date(datetime(2024, 8, 22, 15))): + payload = factory_eft_account_payload(payment_method=PaymentMethod.EFT.value, + account_id=account.auth_account_id) + PaymentAccountService.update(account.auth_account_id, payload) + inv = factory_invoice(payment_account=account, + payment_method_code=PaymentMethod.EFT.value, + status_code=InvoiceStatus.PAID.value, + total=31.50, + created_on=datetime(2024, 8, 22, 15, 15)).save() + invoice_ids.append(inv.id) + # Intentional to generate the statements after the interim statement already exists. + generate_statements(0, 32, override_start=datetime(2024, 8, 1, 15)) + case 'non_interim': + with freeze_time(datetime(2024, 1, 1, 8)): + # This should create a gap between 28th Sunday and 31st Wednesday, this is a gap statement. + StatementSettingsService.update_statement_settings(account.auth_account_id, + StatementFrequency.MONTHLY.value) + generate_statements(0, 32) + case 'pad_to_eft': + # Note: This will work even if we start off as MONTHLY or change to MONTHLY from weekly. + # Generate up to the 28th before the interm statment. + generate_statements(0, 28) + with freeze_time(localize_date(datetime(2024, 1, 28, 8))): + payload = factory_eft_account_payload(payment_method=PaymentMethod.EFT.value, + account_id=account.auth_account_id) + PaymentAccountService.update(account.auth_account_id, payload) + generate_statements(29, 32) + case 'eft_to_pad': + # Generate up to the 28th before the interm statment. + generate_statements(0, 28) + with freeze_time(localize_date(datetime(2024, 1, 28, 8))): + # Update to PAD - Keep the pad activation_date in the past, otherwise we wont switch over to PAD. + # If we can't switch to PAD, no invoices would be created until activation date was hit. + account.pad_activation_date = datetime.now(tz=timezone.utc) - timedelta(days=1) + account.save() + PaymentAccountService.update(account.auth_account_id, factory_pad_account_payload()) + # The default for PAD is weekly, need extra days because no to_date set for gap_statements. + generate_statements(29, 35) + + statements = Statement.query.all() + for statement in statements: + assert statement.payment_methods != 'PAD,EFT' + assert statement.payment_methods != 'EFT,PAD' + + weekly_statements = Statement.query.filter(StatementFrequency.WEEKLY.value == Statement.frequency).all() + sorted_statements = sorted(weekly_statements, key=lambda x: x.from_date) + for prev, current in zip(sorted_statements, sorted_statements[1:]): + # Monthly can overlap with weekly, think of switching from PAD -> EFT on the Jan 24th. + # we'd still need to generate EFT for the entire month of January 1 -> 31st. + # Ensure weekly doesn't overlap with other weekly (interim or gap or weekly). + assert prev.to_date < current.from_date, \ + f'Overlap detected between weekly/gap/interim statements {prev.id} - {current.id}' + + monthly_statements = Statement.query.filter(StatementFrequency.MONTHLY.value == Statement.frequency).all() + sorted_statements = sorted(monthly_statements, key=lambda x: x.from_date) + for prev, current in zip(sorted_statements, sorted_statements[1:]): + # Monthly should never overlap with monthly. + assert prev.to_date < current.from_date, f'Overlap detected between monthly statements {prev.id} - {current.id}' + + generated_invoice_ids = [inv.invoice_id for inv in StatementInvoices.query.all()] + + assert len(invoice_ids) == len(generated_invoice_ids) + + +def generate_statements(start, end, override_start=None): + """Generate statements helper.""" + for r in range(start, end): + target = (override_start or datetime(2024, 1, 1, 8)) + override_date = localize_date(target + timedelta(days=r - 1)).strftime('%Y-%m-%d') + StatementTask.generate_statements([override_date]) + + +def localize_date(date: datetime): + """Localize date object by adding timezone information.""" + pst = pytz.timezone('America/Vancouver') + return pst.localize(date) diff --git a/jobs/payment-jobs/tests/jobs/test_unpaid_invoice_notifytask.py b/jobs/payment-jobs/tests/jobs/test_unpaid_invoice_notifytask.py index d31333136..ef338ce45 100644 --- a/jobs/payment-jobs/tests/jobs/test_unpaid_invoice_notifytask.py +++ b/jobs/payment-jobs/tests/jobs/test_unpaid_invoice_notifytask.py @@ -17,7 +17,7 @@ Test-Suite to ensure that the CreateInvoiceTask is working as expected. """ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from unittest.mock import patch from flask import current_app @@ -39,7 +39,7 @@ def test_unpaid_one_invoice(session): # Create an invoice for this account cfs_account = CfsAccountModel.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) - invoice = factory_invoice(payment_account=account, created_on=datetime.now(), total=10, + invoice = factory_invoice(payment_account=account, created_on=datetime.now(tz=timezone.utc), total=10, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) assert invoice.invoice_status_code == InvoiceStatus.CREATED.value @@ -51,21 +51,21 @@ def test_unpaid_one_invoice(session): time_delay = current_app.config['NOTIFY_AFTER_DAYS'] # invoke one day before the time delay ;shud be no mail - day_after_time_delay = datetime.today() + timedelta(days=(time_delay - 1)) + day_after_time_delay = datetime.now(tz=timezone.utc) + timedelta(days=(time_delay - 1)) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() mock_mailer.assert_not_called() # exact day , mail shud be invoked - day_after_time_delay = datetime.today() + timedelta(days=time_delay) + day_after_time_delay = datetime.now(tz=timezone.utc) + timedelta(days=time_delay) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() mock_mailer.assert_called() # after the time delay day ;shud not get sent - day_after_time_delay = datetime.today() + timedelta(days=time_delay + 1) + day_after_time_delay = datetime.now(tz=timezone.utc) + timedelta(days=time_delay + 1) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() @@ -80,27 +80,27 @@ def test_unpaid_multiple_invoice(session): # Create an invoice for this account cfs_account = CfsAccountModel.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) - invoice = factory_invoice(payment_account=account, created_on=datetime.now(), total=10, + invoice = factory_invoice(payment_account=account, created_on=datetime.now(tz=timezone.utc), total=10, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) assert invoice.invoice_status_code == InvoiceStatus.CREATED.value - factory_invoice(payment_account=account, created_on=datetime.now(), total=200, + factory_invoice(payment_account=account, created_on=datetime.now(tz=timezone.utc), total=200, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) - previous_day = datetime.now() - timedelta(days=1) + previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) factory_invoice(payment_account=account, created_on=previous_day, total=2000, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) # created two invoices ; so two events time_delay = current_app.config['NOTIFY_AFTER_DAYS'] - day_after_time_delay = datetime.today() + timedelta(days=time_delay) + day_after_time_delay = datetime.now(tz=timezone.utc) + timedelta(days=time_delay) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() assert mock_mailer.call_count == 1 # created one invoice yesterday ; so assert one - day_after_time_delay = datetime.today() + timedelta(days=time_delay - 1) + day_after_time_delay = datetime.now(tz=timezone.utc) + timedelta(days=time_delay - 1) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() @@ -114,13 +114,13 @@ def test_unpaid_invoice_pad(session): # Create an invoice for this account cfs_account = CfsAccountModel.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) - invoice = factory_invoice(payment_account=account, created_on=datetime.now(), total=10, + invoice = factory_invoice(payment_account=account, created_on=datetime.now(tz=timezone.utc), total=10, cfs_account_id=cfs_account.id) assert invoice.invoice_status_code == InvoiceStatus.CREATED.value # invoke today ;no mail time_delay = current_app.config['NOTIFY_AFTER_DAYS'] - day_after_time_delay = datetime.today() + timedelta(days=time_delay) + day_after_time_delay = datetime.now(tz=timezone.utc) + timedelta(days=time_delay) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() @@ -138,24 +138,24 @@ def test_unpaid_single_invoice_total(session): total_invoice1 = 100 total_invoice2 = 200 - invoice = factory_invoice(payment_account=account, created_on=datetime.now(), total=total_invoice1, + invoice = factory_invoice(payment_account=account, created_on=datetime.now(tz=timezone.utc), total=total_invoice1, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) assert invoice.invoice_status_code == InvoiceStatus.CREATED.value - previous_day = datetime.now() - timedelta(days=1) + previous_day = datetime.now(tz=timezone.utc) - timedelta(days=1) factory_invoice(payment_account=account, created_on=previous_day, total=total_invoice2, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) # created two invoices ; so two events time_delay = current_app.config['NOTIFY_AFTER_DAYS'] - day_after_time_delay = datetime.today() + timedelta(days=time_delay) + day_after_time_delay = datetime.now(tz=timezone.utc) + timedelta(days=time_delay) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() assert mock_mailer.call_args.args[2].get('transactionAmount') == total_invoice1 + total_invoice2 # created one invoice yesterday ; so assert one - day_after_time_delay = datetime.today() + timedelta(days=time_delay - 1) + day_after_time_delay = datetime.now(tz=timezone.utc) + timedelta(days=time_delay - 1) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() @@ -175,20 +175,21 @@ def test_unpaid_multiple_invoice_total(session): total_invoice2 = 200 total_invoice3 = 300 - invoice = factory_invoice(payment_account=account, created_on=datetime.now(), total=total_invoice1, + invoice = factory_invoice(payment_account=account, created_on=datetime.now(tz=timezone.utc), total=total_invoice1, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) assert invoice.invoice_status_code == InvoiceStatus.CREATED.value - factory_invoice(payment_account=account, created_on=datetime.now(), total=total_invoice2, + factory_invoice(payment_account=account, created_on=datetime.now(tz=timezone.utc), total=total_invoice2, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) # this is future invoice - factory_invoice(payment_account=account, created_on=datetime.now() + timedelta(days=1), total=total_invoice3, + factory_invoice(payment_account=account, created_on=datetime.now(tz=timezone.utc) + timedelta(days=1), + total=total_invoice3, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) # created two invoices ; so two events time_delay = current_app.config['NOTIFY_AFTER_DAYS'] - day_after_time_delay = datetime.today() + timedelta(days=time_delay) + day_after_time_delay = datetime.now(tz=timezone.utc) + timedelta(days=time_delay) total_amount = total_invoice1 + total_invoice2 + total_invoice3 # total amount is the same for any day invocation with freeze_time(day_after_time_delay): @@ -197,7 +198,7 @@ def test_unpaid_multiple_invoice_total(session): assert mock_mailer.call_count == 1 assert mock_mailer.call_args.args[2].get('transactionAmount') == total_amount - one_more_day_delay = datetime.today() + timedelta(days=time_delay + 1) + one_more_day_delay = datetime.now(tz=timezone.utc) + timedelta(days=time_delay + 1) with freeze_time(one_more_day_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() diff --git a/jobs/payment-jobs/utils/auth_event.py b/jobs/payment-jobs/utils/auth_event.py index 9e752fcc5..0d776fd16 100644 --- a/jobs/payment-jobs/utils/auth_event.py +++ b/jobs/payment-jobs/utils/auth_event.py @@ -12,16 +12,16 @@ class AuthEvent: """Publishes to the auth-queue as an auth event though PUBSUB, this message gets sent to account-mailer after.""" @staticmethod - def publish_lock_account_event(pay_account: PaymentAccountModel): - """Publish payment message to the mailer queue.""" + def publish_lock_account_event(pay_account: PaymentAccountModel, additional_emails=''): + """Publish NSF lock account event to the auth queue.""" try: - payload = AuthEvent._create_event_payload(pay_account) + payload = AuthEvent._create_event_payload(pay_account, additional_emails) gcp_queue_publisher.publish_to_queue( QueueMessage( source=QueueSources.PAY_JOBS.value, message_type=QueueMessageTypes.NSF_LOCK_ACCOUNT.value, payload=payload, - topic=current_app.config.get('AUTH_QUEUE_TOPIC') + topic=current_app.config.get('AUTH_EVENT_TOPIC') ) ) except Exception: # NOQA pylint: disable=broad-except @@ -32,9 +32,33 @@ def publish_lock_account_event(pay_account: PaymentAccountModel): pay_account.auth_account_id}, {payload}.', level='error') @staticmethod - def _create_event_payload(pay_account): + def publish_unlock_account_event(payment_account: PaymentAccountModel): + """Publish NSF unlock event to the auth queue.""" + try: + unlock_payload = { + 'accountId': payment_account.auth_account_id, + 'skipNotification': True + } + gcp_queue_publisher.publish_to_queue( + QueueMessage( + source=QueueSources.PAY_JOBS.value, + message_type=QueueMessageTypes.NSF_UNLOCK_ACCOUNT.value, + payload=unlock_payload, + topic=current_app.config.get('AUTH_EVENT_TOPIC') + ) + ) + except Exception: # NOQA pylint: disable=broad-except + current_app.logger.error('Error publishing NSF unlock event:', exc_info=True) + current_app.logger.warning(f'Notification to Queue failed for the Account { + payment_account.auth_account_id} - {payment_account.name}') + capture_message(f'Notification to Queue failed for the Account { + payment_account.auth_account_id}, {unlock_payload}.', level='error') + + @staticmethod + def _create_event_payload(pay_account, additional_emails=''): return { 'accountId': pay_account.auth_account_id, 'paymentMethod': PaymentMethod.EFT.value, - 'suspensionReasonCode': SuspensionReasonCodes.OVERDUE_EFT.value + 'suspensionReasonCode': SuspensionReasonCodes.OVERDUE_EFT.value, + 'additionalEmails': additional_emails } diff --git a/jobs/payment-jobs/utils/mailer.py b/jobs/payment-jobs/utils/mailer.py index bd3521dc4..4e8250843 100644 --- a/jobs/payment-jobs/utils/mailer.py +++ b/jobs/payment-jobs/utils/mailer.py @@ -37,6 +37,7 @@ class StatementNotificationInfo: due_date: datetime emails: str total_amount_owing: float + short_name_links_count: int def publish_mailer_events(message_type: str, pay_account: PaymentAccountModel, additional_params=None): @@ -104,7 +105,8 @@ def publish_statement_notification(pay_account: PaymentAccountModel, statement: def publish_payment_notification(info: StatementNotificationInfo) -> bool: """Publish payment notification message to the mailer queue.""" - message_type = QueueMessageTypes.PAYMENT_DUE_NOTIFICATION.value if info.is_due \ + message_type = QueueMessageTypes.PAYMENT_DUE_NOTIFICATION.value \ + if info.action == StatementNotificationAction.OVERDUE \ else QueueMessageTypes.PAYMENT_REMINDER_NOTIFICATION.value payload = { @@ -112,7 +114,10 @@ def publish_payment_notification(info: StatementNotificationInfo) -> bool: 'accountId': info.auth_account_id, 'dueDate': f'{info.due_date}', 'statementFrequency': info.statement.frequency, - 'totalAmountOwing': info.total_amount_owing + 'statementMonth': info.statement.from_date.strftime('%B'), + 'statementNumber': info.statement.id, + 'totalAmountOwing': info.total_amount_owing, + 'shortNameLinksCount': info.short_name_links_count } try: gcp_queue_publisher.publish_to_queue( diff --git a/pay-admin/Dockerfile b/pay-admin/Dockerfile index 37e8e35e4..e0e950ee9 100644 --- a/pay-admin/Dockerfile +++ b/pay-admin/Dockerfile @@ -71,6 +71,7 @@ COPY --chown=web:web ./README.md /code RUN --mount=type=cache,target="$POETRY_CACHE_DIR" \ echo "$APP_ENV" \ && poetry version \ + && poetry config installer.max-workers 1 \ # Install deps: && poetry run pip install -U pip \ && poetry install \ diff --git a/pay-admin/Makefile b/pay-admin/Makefile index 7548c7e4b..d2ae55707 100755 --- a/pay-admin/Makefile +++ b/pay-admin/Makefile @@ -39,6 +39,7 @@ clean-test: ## clean test files install: clean unset HOME ## unset HOME because it's in the DEV .env file, will cause permissions issues pip install poetry ;\ + poetry config installer.max-workers 1 poetry install ################################################################################# diff --git a/pay-admin/admin/version.py b/pay-admin/admin/version.py index 9576ba6f8..6f57debc9 100755 --- a/pay-admin/admin/version.py +++ b/pay-admin/admin/version.py @@ -22,4 +22,4 @@ Development release segment: .devN """ -__version__ = '0.1.0a0.dev' # pylint: disable=invalid-name +__version__ = '1.0.0' # pylint: disable=invalid-name diff --git a/pay-admin/admin/views/distribution_code.py b/pay-admin/admin/views/distribution_code.py index b75d9836c..faec1daaf 100644 --- a/pay-admin/admin/views/distribution_code.py +++ b/pay-admin/admin/views/distribution_code.py @@ -12,7 +12,7 @@ See the License for the specific language governing permissions and limitations under the License. """ -from pay_api.models import DistributionCode, db +from pay_api.models import DistributionCode, PaymentAccount, db from .secured_view import SecuredView @@ -43,7 +43,12 @@ class DistributionCodeConfig(SecuredView): column_default_sort = 'name' - form_args = {} + form_args = { + 'account': { + 'query_factory': lambda: db.session.query(PaymentAccount) + .filter(PaymentAccount.payment_method == 'EJV').all() + } + } form_columns = edit_columns = [ 'name', 'stop_ejv', 'client', 'responsibility_centre', 'service_line', 'stob', 'project_code', diff --git a/pay-admin/devops/vaults.json b/pay-admin/devops/vaults.json new file mode 100644 index 000000000..e7a453f1d --- /dev/null +++ b/pay-admin/devops/vaults.json @@ -0,0 +1,15 @@ +[ + { + "vault": "relationship", + "application": [ + "postgres-pay", + "pay-admin" + ] + }, + { + "vault": "sentry", + "application": [ + "relationship-api" + ] + } +] diff --git a/pay-admin/logging.conf b/pay-admin/logging.conf index 6cb281bf2..bacdbdf10 100755 --- a/pay-admin/logging.conf +++ b/pay-admin/logging.conf @@ -23,6 +23,12 @@ handlers=console qualname=jaeger_tracing propagate=0 +[logger_sqlalchemy.engine.Engine] +level=DEBUG +handlers=console +qualname=sqlalchemy.engine.Engine +propagate=0 + [handler_console] class=StreamHandler level=DEBUG diff --git a/pay-admin/poetry.lock b/pay-admin/poetry.lock index 998ead2a9..a9cfb8ee5 100644 --- a/pay-admin/poetry.lock +++ b/pay-admin/poetry.lock @@ -1,91 +1,103 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] -name = "aiohttp" -version = "3.9.5" -description = "Async http client/server framework (asyncio)" +name = "aiohappyeyeballs" +version = "2.3.5" +description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, - {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, - {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, - {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, - {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, - {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, - {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, - {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, - {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, - {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, + {file = "aiohappyeyeballs-2.3.5-py3-none-any.whl", hash = "sha256:4d6dea59215537dbc746e93e779caea8178c866856a721c9c660d7a5a7b8be03"}, + {file = "aiohappyeyeballs-2.3.5.tar.gz", hash = "sha256:6fa48b9f1317254f122a07a131a86b71ca6946ca989ce6326fff54a99a920105"}, ] -[package.dependencies] +[[package]] +name = "aiohttp" +version = "3.10.2" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95213b3d79c7e387144e9cb7b9d2809092d6ff2c044cb59033aedc612f38fb6d"}, + {file = "aiohttp-3.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1aa005f060aff7124cfadaa2493f00a4e28ed41b232add5869e129a2e395935a"}, + {file = "aiohttp-3.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eabe6bf4c199687592f5de4ccd383945f485779c7ffb62a9b9f1f8a3f9756df8"}, + {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e010736fc16d21125c7e2dc5c350cd43c528b85085c04bf73a77be328fe944"}, + {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99f81f9c1529fd8e03be4a7bd7df32d14b4f856e90ef6e9cbad3415dbfa9166c"}, + {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d611d1a01c25277bcdea06879afbc11472e33ce842322496b211319aa95441bb"}, + {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00191d38156e09e8c81ef3d75c0d70d4f209b8381e71622165f22ef7da6f101"}, + {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74c091a5ded6cb81785de2d7a8ab703731f26de910dbe0f3934eabef4ae417cc"}, + {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:18186a80ec5a701816adbf1d779926e1069392cf18504528d6e52e14b5920525"}, + {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5a7ceb2a0d2280f23a02c64cd0afdc922079bb950400c3dd13a1ab2988428aac"}, + {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8bd7be6ff6c162a60cb8fce65ee879a684fbb63d5466aba3fa5b9288eb04aefa"}, + {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fae962b62944eaebff4f4fddcf1a69de919e7b967136a318533d82d93c3c6bd1"}, + {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a0fde16d284efcacbe15fb0c1013f0967b6c3e379649239d783868230bf1db42"}, + {file = "aiohttp-3.10.2-cp310-cp310-win32.whl", hash = "sha256:f81cd85a0e76ec7b8e2b6636fe02952d35befda4196b8c88f3cec5b4fb512839"}, + {file = "aiohttp-3.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:54ba10eb5a3481c28282eb6afb5f709aedf53cf9c3a31875ffbdc9fc719ffd67"}, + {file = "aiohttp-3.10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87fab7f948e407444c2f57088286e00e2ed0003ceaf3d8f8cc0f60544ba61d91"}, + {file = "aiohttp-3.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec6ad66ed660d46503243cbec7b2b3d8ddfa020f984209b3b8ef7d98ce69c3f2"}, + {file = "aiohttp-3.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a4be88807283bd96ae7b8e401abde4ca0bab597ba73b5e9a2d98f36d451e9aac"}, + {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01c98041f90927c2cbd72c22a164bb816fa3010a047d264969cf82e1d4bcf8d1"}, + {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54e36c67e1a9273ecafab18d6693da0fb5ac48fd48417e4548ac24a918c20998"}, + {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7de3ddb6f424af54535424082a1b5d1ae8caf8256ebd445be68c31c662354720"}, + {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dd9c7db94b4692b827ce51dcee597d61a0e4f4661162424faf65106775b40e7"}, + {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e57e21e1167705f8482ca29cc5d02702208d8bf4aff58f766d94bcd6ead838cd"}, + {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a1a50e59b720060c29e2951fd9f13c01e1ea9492e5a527b92cfe04dd64453c16"}, + {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:686c87782481fda5ee6ba572d912a5c26d9f98cc5c243ebd03f95222af3f1b0f"}, + {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:dafb4abb257c0ed56dc36f4e928a7341b34b1379bd87e5a15ce5d883c2c90574"}, + {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:494a6f77560e02bd7d1ab579fdf8192390567fc96a603f21370f6e63690b7f3d"}, + {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6fe8503b1b917508cc68bf44dae28823ac05e9f091021e0c41f806ebbb23f92f"}, + {file = "aiohttp-3.10.2-cp311-cp311-win32.whl", hash = "sha256:4ddb43d06ce786221c0dfd3c91b4892c318eaa36b903f7c4278e7e2fa0dd5102"}, + {file = "aiohttp-3.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:ca2f5abcb0a9a47e56bac173c01e9f6c6e7f27534d91451c5f22e6a35a5a2093"}, + {file = "aiohttp-3.10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:14eb6b17f6246959fb0b035d4f4ae52caa870c4edfb6170aad14c0de5bfbf478"}, + {file = "aiohttp-3.10.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:465e445ec348d4e4bd349edd8b22db75f025da9d7b6dc1369c48e7935b85581e"}, + {file = "aiohttp-3.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:341f8ece0276a828d95b70cd265d20e257f5132b46bf77d759d7f4e0443f2906"}, + {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c01fbb87b5426381cd9418b3ddcf4fc107e296fa2d3446c18ce6c76642f340a3"}, + {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c474af073e1a6763e1c5522bbb2d85ff8318197e4c6c919b8d7886e16213345"}, + {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d9076810a5621236e29b2204e67a68e1fe317c8727ee4c9abbfbb1083b442c38"}, + {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8f515d6859e673940e08de3922b9c4a2249653b0ac181169313bd6e4b1978ac"}, + {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:655e583afc639bef06f3b2446972c1726007a21003cd0ef57116a123e44601bc"}, + {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8da9449a575133828cc99985536552ea2dcd690e848f9d41b48d8853a149a959"}, + {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:19073d57d0feb1865d12361e2a1f5a49cb764bf81a4024a3b608ab521568093a"}, + {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8e98e1845805f184d91fda6f9ab93d7c7b0dddf1c07e0255924bfdb151a8d05"}, + {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:377220a5efde6f9497c5b74649b8c261d3cce8a84cb661be2ed8099a2196400a"}, + {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92f7f4a4dc9cdb5980973a74d43cdbb16286dacf8d1896b6c3023b8ba8436f8e"}, + {file = "aiohttp-3.10.2-cp312-cp312-win32.whl", hash = "sha256:9bb2834a6f11d65374ce97d366d6311a9155ef92c4f0cee543b2155d06dc921f"}, + {file = "aiohttp-3.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:518dc3cb37365255708283d1c1c54485bbacccd84f0a0fb87ed8917ba45eda5b"}, + {file = "aiohttp-3.10.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7f98e70bbbf693086efe4b86d381efad8edac040b8ad02821453083d15ec315f"}, + {file = "aiohttp-3.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f6f0b252a009e98fe84028a4ec48396a948e7a65b8be06ccfc6ef68cf1f614d"}, + {file = "aiohttp-3.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9360e3ffc7b23565600e729e8c639c3c50d5520e05fdf94aa2bd859eef12c407"}, + {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3988044d1635c7821dd44f0edfbe47e9875427464e59d548aece447f8c22800a"}, + {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a9d59da1543a6f1478c3436fd49ec59be3868bca561a33778b4391005e499d"}, + {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f49bdb94809ac56e09a310a62f33e5f22973d6fd351aac72a39cd551e98194"}, + {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddfd2dca3f11c365d6857a07e7d12985afc59798458a2fdb2ffa4a0332a3fd43"}, + {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c1508ec97b2cd3e120bfe309a4ff8e852e8a7460f1ef1de00c2c0ed01e33c"}, + {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:49904f38667c44c041a0b44c474b3ae36948d16a0398a8f8cd84e2bb3c42a069"}, + {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:352f3a4e5f11f3241a49b6a48bc5b935fabc35d1165fa0d87f3ca99c1fcca98b"}, + {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:fc61f39b534c5d5903490478a0dd349df397d2284a939aa3cbaa2fb7a19b8397"}, + {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:ad2274e707be37420d0b6c3d26a8115295fe9d8e6e530fa6a42487a8ca3ad052"}, + {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c836bf3c7512100219fe1123743fd8dd9a2b50dd7cfb0c3bb10d041309acab4b"}, + {file = "aiohttp-3.10.2-cp38-cp38-win32.whl", hash = "sha256:53e8898adda402be03ff164b0878abe2d884e3ea03a4701e6ad55399d84b92dc"}, + {file = "aiohttp-3.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:7cc8f65f5b22304693de05a245b6736b14cb5bc9c8a03da6e2ae9ef15f8b458f"}, + {file = "aiohttp-3.10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9dfc906d656e14004c5bc672399c1cccc10db38df2b62a13fb2b6e165a81c316"}, + {file = "aiohttp-3.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:91b10208b222ddf655c3a3d5b727879d7163db12b634492df41a9182a76edaae"}, + {file = "aiohttp-3.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9fd16b5e1a7bdd14668cd6bde60a2a29b49147a535c74f50d8177d11b38433a7"}, + {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2bfdda4971bd79201f59adbad24ec2728875237e1c83bba5221284dbbf57bda"}, + {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69d73f869cf29e8a373127fc378014e2b17bcfbe8d89134bc6fb06a2f67f3cb3"}, + {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df59f8486507c421c0620a2c3dce81fbf1d54018dc20ff4fecdb2c106d6e6abc"}, + {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df930015db36b460aa9badbf35eccbc383f00d52d4b6f3de2ccb57d064a6ade"}, + {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:562b1153ab7f766ee6b8b357ec777a302770ad017cf18505d34f1c088fccc448"}, + {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d984db6d855de58e0fde1ef908d48fe9a634cadb3cf715962722b4da1c40619d"}, + {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:14dc3fcb0d877911d775d511eb617a486a8c48afca0a887276e63db04d3ee920"}, + {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b52a27a5c97275e254704e1049f4b96a81e67d6205f52fa37a4777d55b0e98ef"}, + {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:cd33d9de8cfd006a0d0fe85f49b4183c57e91d18ffb7e9004ce855e81928f704"}, + {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1238fc979160bc03a92fff9ad021375ff1c8799c6aacb0d8ea1b357ea40932bb"}, + {file = "aiohttp-3.10.2-cp39-cp39-win32.whl", hash = "sha256:e2f43d238eae4f0b04f58d4c0df4615697d4ca3e9f9b1963d49555a94f0f5a04"}, + {file = "aiohttp-3.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:947847f07a8f81d7b39b2d0202fd73e61962ebe17ac2d8566f260679e467da7b"}, + {file = "aiohttp-3.10.2.tar.gz", hash = "sha256:4d1f694b5d6e459352e5e925a42e05bac66655bfde44d81c59992463d2897014"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" aiosignal = ">=1.1.2" attrs = ">=17.3.0" frozenlist = ">=1.1.1" @@ -93,7 +105,7 @@ multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "brotlicffi"] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] [[package]] name = "aiosignal" @@ -141,13 +153,13 @@ files = [ [[package]] name = "astroid" -version = "3.2.2" +version = "3.2.4" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.8.0" files = [ - {file = "astroid-3.2.2-py3-none-any.whl", hash = "sha256:e8a0083b4bb28fcffb6207a3bfc9e5d0a68be951dd7e336d5dcf639c682388c0"}, - {file = "astroid-3.2.2.tar.gz", hash = "sha256:8ead48e31b92b2e217b6c9733a21afafe479d52d6e164dd25fb1a770c7c3cf94"}, + {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"}, + {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, ] [[package]] @@ -185,17 +197,17 @@ cryptography = "*" [[package]] name = "autopep8" -version = "2.2.0" +version = "2.3.1" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" optional = false python-versions = ">=3.8" files = [ - {file = "autopep8-2.2.0-py2.py3-none-any.whl", hash = "sha256:05418a981f038969d8bdcd5636bf15948db7555ae944b9f79b5a34b35f1370d4"}, - {file = "autopep8-2.2.0.tar.gz", hash = "sha256:d306a0581163ac29908280ad557773a95a9bede072c0fafed6f141f5311f43c1"}, + {file = "autopep8-2.3.1-py2.py3-none-any.whl", hash = "sha256:a203fe0fcad7939987422140ab17a930f684763bf7335bdb6709991dd7ef6c2d"}, + {file = "autopep8-2.3.1.tar.gz", hash = "sha256:8d6c87eba648fdcfc83e29b788910b8643171c395d9c4bcf115ece035b9c9dda"}, ] [package.dependencies] -pycodestyle = ">=2.11.0" +pycodestyle = ">=2.12.0" [[package]] name = "blinker" @@ -255,13 +267,13 @@ ujson = ["ujson (>=5.7.0)"] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.7.4" 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"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -454,63 +466,63 @@ files = [ [[package]] name = "coverage" -version = "7.5.3" +version = "7.6.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, - {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, - {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, - {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, - {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, - {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, - {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, - {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, - {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, - {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, - {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, - {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, - {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, - {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, + {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, + {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, + {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, + {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, + {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, + {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, + {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, + {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, + {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, + {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, + {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, + {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, ] [package.extras] @@ -533,43 +545,38 @@ pytz = ">2021.1" [[package]] name = "cryptography" -version = "42.0.5" +version = "43.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, + {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, + {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, + {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, + {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, + {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, + {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, + {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, ] [package.dependencies] @@ -582,7 +589,7 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] @@ -645,18 +652,18 @@ tests = ["coverage", "coveralls", "dill", "mock", "nose"] [[package]] name = "flake8" -version = "7.0.0" +version = "7.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" files = [ - {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, - {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, + {file = "flake8-7.1.0-py2.py3-none-any.whl", hash = "sha256:2e416edcc62471a64cea09353f4e7bdba32aeb079b6e360554c659a122b1bc6a"}, + {file = "flake8-7.1.0.tar.gz", hash = "sha256:48a07b626b55236e0fb4784ee69a465fbf59d79eec1f5b4785c3d3bc57d17aa5"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.11.0,<2.12.0" +pycodestyle = ">=2.12.0,<2.13.0" pyflakes = ">=3.2.0,<3.3.0" [[package]] @@ -789,13 +796,13 @@ Flask = "*" [[package]] name = "flask-cors" -version = "4.0.0" +version = "5.0.0" description = "A Flask extension adding a decorator for CORS support" optional = false python-versions = "*" files = [ - {file = "Flask-Cors-4.0.0.tar.gz", hash = "sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"}, - {file = "Flask_Cors-4.0.0-py2.py3-none-any.whl", hash = "sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783"}, + {file = "Flask_Cors-5.0.0-py2.py3-none-any.whl", hash = "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc"}, + {file = "flask_cors-5.0.0.tar.gz", hash = "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef"}, ] [package.dependencies] @@ -876,17 +883,18 @@ packaging = ">=14.1" [[package]] name = "flask-oidc" -version = "2.1.1" +version = "2.2.0" description = "OpenID Connect extension for Flask" optional = false -python-versions = ">=3.8,<4.0" +python-versions = "<4.0,>=3.8" files = [ - {file = "flask_oidc-2.1.1-py3-none-any.whl", hash = "sha256:379be8fa15c275a2cbbb0e179eb1e937d3c9cf0788f39bad047cdd99f40613b1"}, - {file = "flask_oidc-2.1.1.tar.gz", hash = "sha256:9a283cf760f9d3053ef71280705fbefeb62d1baa0fdceb4a99784ab01ba8e307"}, + {file = "flask_oidc-2.2.0-py3-none-any.whl", hash = "sha256:879ac475ed5164d1ab902026a4fbc2957817a56db30e288267f47028b006a072"}, + {file = "flask_oidc-2.2.0.tar.gz", hash = "sha256:72bb679fd78d8066925bf2f8e8f5ace03353421451fd2577f2bb57766d572892"}, ] [package.dependencies] authlib = ">=1.2.0,<2.0.0" +blinker = ">=1.5.0,<2.0.0" flask = ">=2.0.0,<4.0.0" requests = ">=2.24.0,<3.0.0" @@ -1078,18 +1086,18 @@ simple-cloudevent = {git = "https://github.com/daxiom/simple-cloudevent.py.git"} type = "git" url = "https://github.com/bcgov/sbc-connect-common.git" reference = "main" -resolved_reference = "c348d30e91253daef7f433a15a12379eb6b68d55" +resolved_reference = "c898988d239dc261b2b186465a1887f15512c102" subdirectory = "python/gcp-queue" [[package]] name = "google-api-core" -version = "2.19.0" +version = "2.19.1" description = "Google API client core library" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-core-2.19.0.tar.gz", hash = "sha256:cf1b7c2694047886d2af1128a03ae99e391108a08804f87cfd35970e49c9cd10"}, - {file = "google_api_core-2.19.0-py3-none-any.whl", hash = "sha256:8661eec4078c35428fd3f69a2c7ee29e342896b70f01d1a1cbcb334372dd6251"}, + {file = "google-api-core-2.19.1.tar.gz", hash = "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd"}, + {file = "google_api_core-2.19.1-py3-none-any.whl", hash = "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125"}, ] [package.dependencies] @@ -1098,7 +1106,7 @@ googleapis-common-protos = ">=1.56.2,<2.0.dev0" grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" requests = ">=2.18.0,<3.0.0.dev0" [package.extras] @@ -1108,13 +1116,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-auth" -version = "2.29.0" +version = "2.32.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google-auth-2.29.0.tar.gz", hash = "sha256:672dff332d073227550ffc7457868ac4218d6c500b155fe6cc17d2b13602c360"}, - {file = "google_auth-2.29.0-py2.py3-none-any.whl", hash = "sha256:d452ad095688cd52bae0ad6fafe027f6a6d6f560e810fec20914e17a09526415"}, + {file = "google_auth-2.32.0-py2.py3-none-any.whl", hash = "sha256:53326ea2ebec768070a94bee4e1b9194c9646ea0c2bd72422785bd0f9abfad7b"}, + {file = "google_auth-2.32.0.tar.gz", hash = "sha256:49315be72c55a6a37d62819e3573f6b416aca00721f7e3e31a008d928bf64022"}, ] [package.dependencies] @@ -1131,13 +1139,13 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "google-cloud-pubsub" -version = "2.21.2" +version = "2.22.0" description = "Google Cloud Pub/Sub API client library" optional = false python-versions = ">=3.7" files = [ - {file = "google-cloud-pubsub-2.21.2.tar.gz", hash = "sha256:fc72226b14731db2873f7c4031cc757e274bbcdabcac7523b2cd6e46130d6096"}, - {file = "google_cloud_pubsub-2.21.2-py2.py3-none-any.whl", hash = "sha256:05a6b01e5bda6f4a4858700e3e9a12e3080589718d648b2383e5818131db9ce4"}, + {file = "google_cloud_pubsub-2.22.0-py2.py3-none-any.whl", hash = "sha256:229bf60a3835c1bb21ee36c7d4368b111097678b8ed25d3fbc5e639a1d03388d"}, + {file = "google_cloud_pubsub-2.22.0.tar.gz", hash = "sha256:a4c2b1a5ca2c0b32c8d3776c85f498266c3d79696696ea67010c857b45af17d8"}, ] [package.dependencies] @@ -1147,25 +1155,25 @@ grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" grpcio = ">=1.51.3,<2.0dev" grpcio-status = ">=1.33.2" proto-plus = {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" +protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" [package.extras] libcst = ["libcst (>=0.3.10)"] [[package]] name = "googleapis-common-protos" -version = "1.63.1" +version = "1.63.2" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.63.1.tar.gz", hash = "sha256:c6442f7a0a6b2a80369457d79e6672bb7dcbaab88e0848302497e3ec80780a6a"}, - {file = "googleapis_common_protos-1.63.1-py2.py3-none-any.whl", hash = "sha256:0e1c2cdfcbc354b76e4a211a35ea35d6926a835cba1377073c4861db904a1877"}, + {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, + {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, ] [package.dependencies] grpcio = {version = ">=1.44.0,<2.0.0.dev0", optional = true, markers = "extra == \"grpc\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] @@ -1243,77 +1251,77 @@ test = ["objgraph", "psutil"] [[package]] name = "grpc-google-iam-v1" -version = "0.13.0" +version = "0.13.1" description = "IAM API client library" optional = false python-versions = ">=3.7" files = [ - {file = "grpc-google-iam-v1-0.13.0.tar.gz", hash = "sha256:fad318608b9e093258fbf12529180f400d1c44453698a33509cc6ecf005b294e"}, - {file = "grpc_google_iam_v1-0.13.0-py2.py3-none-any.whl", hash = "sha256:53902e2af7de8df8c1bd91373d9be55b0743ec267a7428ea638db3775becae89"}, + {file = "grpc-google-iam-v1-0.13.1.tar.gz", hash = "sha256:3ff4b2fd9d990965e410965253c0da6f66205d5a8291c4c31c6ebecca18a9001"}, + {file = "grpc_google_iam_v1-0.13.1-py2.py3-none-any.whl", hash = "sha256:c3e86151a981811f30d5e7330f271cee53e73bb87755e88cc3b6f0c7b5fe374e"}, ] [package.dependencies] googleapis-common-protos = {version = ">=1.56.0,<2.0.0dev", extras = ["grpc"]} grpcio = ">=1.44.0,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" [[package]] name = "grpcio" -version = "1.64.1" +version = "1.65.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.64.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:55697ecec192bc3f2f3cc13a295ab670f51de29884ca9ae6cd6247df55df2502"}, - {file = "grpcio-1.64.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:3b64ae304c175671efdaa7ec9ae2cc36996b681eb63ca39c464958396697daff"}, - {file = "grpcio-1.64.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:bac71b4b28bc9af61efcdc7630b166440bbfbaa80940c9a697271b5e1dabbc61"}, - {file = "grpcio-1.64.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c024ffc22d6dc59000faf8ad781696d81e8e38f4078cb0f2630b4a3cf231a90"}, - {file = "grpcio-1.64.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7cd5c1325f6808b8ae31657d281aadb2a51ac11ab081ae335f4f7fc44c1721d"}, - {file = "grpcio-1.64.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0a2813093ddb27418a4c99f9b1c223fab0b053157176a64cc9db0f4557b69bd9"}, - {file = "grpcio-1.64.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2981c7365a9353f9b5c864595c510c983251b1ab403e05b1ccc70a3d9541a73b"}, - {file = "grpcio-1.64.1-cp310-cp310-win32.whl", hash = "sha256:1262402af5a511c245c3ae918167eca57342c72320dffae5d9b51840c4b2f86d"}, - {file = "grpcio-1.64.1-cp310-cp310-win_amd64.whl", hash = "sha256:19264fc964576ddb065368cae953f8d0514ecc6cb3da8903766d9fb9d4554c33"}, - {file = "grpcio-1.64.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:58b1041e7c870bb30ee41d3090cbd6f0851f30ae4eb68228955d973d3efa2e61"}, - {file = "grpcio-1.64.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bbc5b1d78a7822b0a84c6f8917faa986c1a744e65d762ef6d8be9d75677af2ca"}, - {file = "grpcio-1.64.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:5841dd1f284bd1b3d8a6eca3a7f062b06f1eec09b184397e1d1d43447e89a7ae"}, - {file = "grpcio-1.64.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8caee47e970b92b3dd948371230fcceb80d3f2277b3bf7fbd7c0564e7d39068e"}, - {file = "grpcio-1.64.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73819689c169417a4f978e562d24f2def2be75739c4bed1992435d007819da1b"}, - {file = "grpcio-1.64.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6503b64c8b2dfad299749cad1b595c650c91e5b2c8a1b775380fcf8d2cbba1e9"}, - {file = "grpcio-1.64.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1de403fc1305fd96cfa75e83be3dee8538f2413a6b1685b8452301c7ba33c294"}, - {file = "grpcio-1.64.1-cp311-cp311-win32.whl", hash = "sha256:d4d29cc612e1332237877dfa7fe687157973aab1d63bd0f84cf06692f04c0367"}, - {file = "grpcio-1.64.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e56462b05a6f860b72f0fa50dca06d5b26543a4e88d0396259a07dc30f4e5aa"}, - {file = "grpcio-1.64.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:4657d24c8063e6095f850b68f2d1ba3b39f2b287a38242dcabc166453e950c59"}, - {file = "grpcio-1.64.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:62b4e6eb7bf901719fce0ca83e3ed474ae5022bb3827b0a501e056458c51c0a1"}, - {file = "grpcio-1.64.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:ee73a2f5ca4ba44fa33b4d7d2c71e2c8a9e9f78d53f6507ad68e7d2ad5f64a22"}, - {file = "grpcio-1.64.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:198908f9b22e2672a998870355e226a725aeab327ac4e6ff3a1399792ece4762"}, - {file = "grpcio-1.64.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39b9d0acaa8d835a6566c640f48b50054f422d03e77e49716d4c4e8e279665a1"}, - {file = "grpcio-1.64.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5e42634a989c3aa6049f132266faf6b949ec2a6f7d302dbb5c15395b77d757eb"}, - {file = "grpcio-1.64.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1a82e0b9b3022799c336e1fc0f6210adc019ae84efb7321d668129d28ee1efb"}, - {file = "grpcio-1.64.1-cp312-cp312-win32.whl", hash = "sha256:55260032b95c49bee69a423c2f5365baa9369d2f7d233e933564d8a47b893027"}, - {file = "grpcio-1.64.1-cp312-cp312-win_amd64.whl", hash = "sha256:c1a786ac592b47573a5bb7e35665c08064a5d77ab88a076eec11f8ae86b3e3f6"}, - {file = "grpcio-1.64.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:a011ac6c03cfe162ff2b727bcb530567826cec85eb8d4ad2bfb4bd023287a52d"}, - {file = "grpcio-1.64.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4d6dab6124225496010bd22690f2d9bd35c7cbb267b3f14e7a3eb05c911325d4"}, - {file = "grpcio-1.64.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:a5e771d0252e871ce194d0fdcafd13971f1aae0ddacc5f25615030d5df55c3a2"}, - {file = "grpcio-1.64.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3c1b90ab93fed424e454e93c0ed0b9d552bdf1b0929712b094f5ecfe7a23ad"}, - {file = "grpcio-1.64.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20405cb8b13fd779135df23fabadc53b86522d0f1cba8cca0e87968587f50650"}, - {file = "grpcio-1.64.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0cc79c982ccb2feec8aad0e8fb0d168bcbca85bc77b080d0d3c5f2f15c24ea8f"}, - {file = "grpcio-1.64.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a3a035c37ce7565b8f4f35ff683a4db34d24e53dc487e47438e434eb3f701b2a"}, - {file = "grpcio-1.64.1-cp38-cp38-win32.whl", hash = "sha256:1257b76748612aca0f89beec7fa0615727fd6f2a1ad580a9638816a4b2eb18fd"}, - {file = "grpcio-1.64.1-cp38-cp38-win_amd64.whl", hash = "sha256:0a12ddb1678ebc6a84ec6b0487feac020ee2b1659cbe69b80f06dbffdb249122"}, - {file = "grpcio-1.64.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:75dbbf415026d2862192fe1b28d71f209e2fd87079d98470db90bebe57b33179"}, - {file = "grpcio-1.64.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e3d9f8d1221baa0ced7ec7322a981e28deb23749c76eeeb3d33e18b72935ab62"}, - {file = "grpcio-1.64.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:5f8b75f64d5d324c565b263c67dbe4f0af595635bbdd93bb1a88189fc62ed2e5"}, - {file = "grpcio-1.64.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c84ad903d0d94311a2b7eea608da163dace97c5fe9412ea311e72c3684925602"}, - {file = "grpcio-1.64.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:940e3ec884520155f68a3b712d045e077d61c520a195d1a5932c531f11883489"}, - {file = "grpcio-1.64.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f10193c69fc9d3d726e83bbf0f3d316f1847c3071c8c93d8090cf5f326b14309"}, - {file = "grpcio-1.64.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac15b6c2c80a4d1338b04d42a02d376a53395ddf0ec9ab157cbaf44191f3ffdd"}, - {file = "grpcio-1.64.1-cp39-cp39-win32.whl", hash = "sha256:03b43d0ccf99c557ec671c7dede64f023c7da9bb632ac65dbc57f166e4970040"}, - {file = "grpcio-1.64.1-cp39-cp39-win_amd64.whl", hash = "sha256:ed6091fa0adcc7e4ff944090cf203a52da35c37a130efa564ded02b7aff63bcd"}, - {file = "grpcio-1.64.1.tar.gz", hash = "sha256:8d51dd1c59d5fa0f34266b80a3805ec29a1f26425c2a54736133f6d87fc4968a"}, + {file = "grpcio-1.65.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:3dc5f928815b8972fb83b78d8db5039559f39e004ec93ebac316403fe031a062"}, + {file = "grpcio-1.65.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:8333ca46053c35484c9f2f7e8d8ec98c1383a8675a449163cea31a2076d93de8"}, + {file = "grpcio-1.65.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:7af64838b6e615fff0ec711960ed9b6ee83086edfa8c32670eafb736f169d719"}, + {file = "grpcio-1.65.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb64b4166362d9326f7efbf75b1c72106c1aa87f13a8c8b56a1224fac152f5c"}, + {file = "grpcio-1.65.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8422dc13ad93ec8caa2612b5032a2b9cd6421c13ed87f54db4a3a2c93afaf77"}, + {file = "grpcio-1.65.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4effc0562b6c65d4add6a873ca132e46ba5e5a46f07c93502c37a9ae7f043857"}, + {file = "grpcio-1.65.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a6c71575a2fedf259724981fd73a18906513d2f306169c46262a5bae956e6364"}, + {file = "grpcio-1.65.1-cp310-cp310-win32.whl", hash = "sha256:34966cf526ef0ea616e008d40d989463e3db157abb213b2f20c6ce0ae7928875"}, + {file = "grpcio-1.65.1-cp310-cp310-win_amd64.whl", hash = "sha256:ca931de5dd6d9eb94ff19a2c9434b23923bce6f767179fef04dfa991f282eaad"}, + {file = "grpcio-1.65.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:bbb46330cc643ecf10bd9bd4ca8e7419a14b6b9dedd05f671c90fb2c813c6037"}, + {file = "grpcio-1.65.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d827a6fb9215b961eb73459ad7977edb9e748b23e3407d21c845d1d8ef6597e5"}, + {file = "grpcio-1.65.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:6e71aed8835f8d9fbcb84babc93a9da95955d1685021cceb7089f4f1e717d719"}, + {file = "grpcio-1.65.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a1c84560b3b2d34695c9ba53ab0264e2802721c530678a8f0a227951f453462"}, + {file = "grpcio-1.65.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27adee2338d697e71143ed147fe286c05810965d5d30ec14dd09c22479bfe48a"}, + {file = "grpcio-1.65.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f62652ddcadc75d0e7aa629e96bb61658f85a993e748333715b4ab667192e4e8"}, + {file = "grpcio-1.65.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:71a05fd814700dd9cb7d9a507f2f6a1ef85866733ccaf557eedacec32d65e4c2"}, + {file = "grpcio-1.65.1-cp311-cp311-win32.whl", hash = "sha256:b590f1ad056294dfaeac0b7e1b71d3d5ace638d8dd1f1147ce4bd13458783ba8"}, + {file = "grpcio-1.65.1-cp311-cp311-win_amd64.whl", hash = "sha256:12e9bdf3b5fd48e5fbe5b3da382ad8f97c08b47969f3cca81dd9b36b86ed39e2"}, + {file = "grpcio-1.65.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:54cb822e177374b318b233e54b6856c692c24cdbd5a3ba5335f18a47396bac8f"}, + {file = "grpcio-1.65.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:aaf3c54419a28d45bd1681372029f40e5bfb58e5265e3882eaf21e4a5f81a119"}, + {file = "grpcio-1.65.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:557de35bdfbe8bafea0a003dbd0f4da6d89223ac6c4c7549d78e20f92ead95d9"}, + {file = "grpcio-1.65.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8bfd95ef3b097f0cc86ade54eafefa1c8ed623aa01a26fbbdcd1a3650494dd11"}, + {file = "grpcio-1.65.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e6a8f3d6c41e6b642870afe6cafbaf7b61c57317f9ec66d0efdaf19db992b90"}, + {file = "grpcio-1.65.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1faaf7355ceed07ceaef0b9dcefa4c98daf1dd8840ed75c2de128c3f4a4d859d"}, + {file = "grpcio-1.65.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:60f1f38eed830488ad2a1b11579ef0f345ff16fffdad1d24d9fbc97ba31804ff"}, + {file = "grpcio-1.65.1-cp312-cp312-win32.whl", hash = "sha256:e75acfa52daf5ea0712e8aa82f0003bba964de7ae22c26d208cbd7bc08500177"}, + {file = "grpcio-1.65.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff5a84907e51924973aa05ed8759210d8cdae7ffcf9e44fd17646cf4a902df59"}, + {file = "grpcio-1.65.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:1fbd6331f18c3acd7e09d17fd840c096f56eaf0ef830fbd50af45ae9dc8dfd83"}, + {file = "grpcio-1.65.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:de5b6be29116e094c5ef9d9e4252e7eb143e3d5f6bd6d50a78075553ab4930b0"}, + {file = "grpcio-1.65.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:e4a3cdba62b2d6aeae6027ae65f350de6dc082b72e6215eccf82628e79efe9ba"}, + {file = "grpcio-1.65.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941c4869aa229d88706b78187d60d66aca77fe5c32518b79e3c3e03fc26109a2"}, + {file = "grpcio-1.65.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f40cebe5edb518d78b8131e87cb83b3ee688984de38a232024b9b44e74ee53d3"}, + {file = "grpcio-1.65.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2ca684ba331fb249d8a1ce88db5394e70dbcd96e58d8c4b7e0d7b141a453dce9"}, + {file = "grpcio-1.65.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8558f0083ddaf5de64a59c790bffd7568e353914c0c551eae2955f54ee4b857f"}, + {file = "grpcio-1.65.1-cp38-cp38-win32.whl", hash = "sha256:8d8143a3e3966f85dce6c5cc45387ec36552174ba5712c5dc6fcc0898fb324c0"}, + {file = "grpcio-1.65.1-cp38-cp38-win_amd64.whl", hash = "sha256:76e81a86424d6ca1ce7c16b15bdd6a964a42b40544bf796a48da241fdaf61153"}, + {file = "grpcio-1.65.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:cb5175f45c980ff418998723ea1b3869cce3766d2ab4e4916fbd3cedbc9d0ed3"}, + {file = "grpcio-1.65.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b12c1aa7b95abe73b3e04e052c8b362655b41c7798da69f1eaf8d186c7d204df"}, + {file = "grpcio-1.65.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:3019fb50128b21a5e018d89569ffaaaa361680e1346c2f261bb84a91082eb3d3"}, + {file = "grpcio-1.65.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ae15275ed98ea267f64ee9ddedf8ecd5306a5b5bb87972a48bfe24af24153e8"}, + {file = "grpcio-1.65.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f096ffb881f37e8d4f958b63c74bfc400c7cebd7a944b027357cd2fb8d91a57"}, + {file = "grpcio-1.65.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2f56b5a68fdcf17a0a1d524bf177218c3c69b3947cb239ea222c6f1867c3ab68"}, + {file = "grpcio-1.65.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:941596d419b9736ab548aa0feb5bbba922f98872668847bf0720b42d1d227b9e"}, + {file = "grpcio-1.65.1-cp39-cp39-win32.whl", hash = "sha256:5fd7337a823b890215f07d429f4f193d24b80d62a5485cf88ee06648591a0c57"}, + {file = "grpcio-1.65.1-cp39-cp39-win_amd64.whl", hash = "sha256:1bceeec568372cbebf554eae1b436b06c2ff24cfaf04afade729fb9035408c6c"}, + {file = "grpcio-1.65.1.tar.gz", hash = "sha256:3c492301988cd720cd145d84e17318d45af342e29ef93141228f9cd73222368b"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.64.1)"] +protobuf = ["grpcio-tools (>=1.65.1)"] [[package]] name = "grpcio-status" @@ -1333,22 +1341,23 @@ protobuf = ">=4.21.6" [[package]] name = "gunicorn" -version = "21.2.0" +version = "22.0.0" description = "WSGI HTTP Server for UNIX" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, - {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, + {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"}, + {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"}, ] [package.dependencies] packaging = "*" [package.extras] -eventlet = ["eventlet (>=0.24.1)"] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] gevent = ["gevent (>=1.4.0)"] setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] tornado = ["tornado (>=0.2)"] [[package]] @@ -1381,13 +1390,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -1447,13 +1456,13 @@ tests = ["codecov", "coverage", "flake8", "flake8-quotes", "flake8-typing-import [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -1848,18 +1857,18 @@ blinker = "1.7.0" cachelib = "0.9.0" cachetools = "5.3.3" cattrs = "23.2.3" -certifi = "2024.2.2" +certifi = "2024.7.4" cffi = "1.16.0" charset-normalizer = "3.3.2" click = "8.1.7" croniter = "2.0.2" -cryptography = "42.0.5" +cryptography = "43.0.1" dpath = "2.1.6" ecdsa = "0.18.0" expiringdict = "1.2.2" flask = "3.0.2" flask-caching = "2.3.0" -flask-cors = "4.0.0" +flask-cors = "5.0.0" flask-jwt-oidc = {git = "https://github.com/seeker25/flask-jwt-oidc.git"} flask-marshmallow = "1.2.0" flask-migrate = "4.0.7" @@ -1868,12 +1877,12 @@ flask-script = "2.0.6" flask-sqlalchemy = "3.1.1" gcp-queue = {git = "https://github.com/bcgov/sbc-connect-common.git", branch = "main", subdirectory = "python/gcp-queue"} greenlet = "3.0.3" -gunicorn = "21.2.0" +gunicorn = "22.0.0" holidays = "0.37" -idna = "3.6" +idna = "3.7" itsdangerous = "2.1.2" jaeger-client = "4.8.0" -jinja2 = "3.1.3" +jinja2 = "3.1.4" jsonschema = "4.17.3" launchdarkly-eventsource = "1.1.1" launchdarkly-server-sdk = "8.2.1" @@ -1901,23 +1910,24 @@ requests = "2.32.2" rsa = "4.9" sbc-common-components = {git = "https://github.com/bcgov/sbc-common-components.git", subdirectory = "python"} semver = "3.0.2" -sentry-sdk = "1.41.0" +sentry-sdk = {version = "^2.8.0", extras = ["flask"]} +setuptools = "^73.0.1" six = "1.16.0" -sql-versioning = {git = "https://github.com/bcgov/lear.git", branch = "feature-legal-name", subdirectory = "python/common/sql-versioning"} +sql-versioning = {git = "https://github.com/bcgov/sbc-connect-common.git", branch = "main", subdirectory = "python/sql-versioning"} sqlalchemy = "2.0.28" sqlalchemy-utils = "0.41.1" threadloop = "1.0.2" thrift = "0.16.0" -tornado = "6.4" +tornado = "6.4.1" typing-extensions = "4.10.0" urllib3 = "2.2.2" -werkzeug = "3.0.1" +werkzeug = "3.0.3" [package.source] type = "git" url = "https://github.com/bcgov/sbc-pay.git" -reference = "HEAD" -resolved_reference = "1d4733acfb5c430fa99d7f88e1be5b1d8f305ead" +reference = "main" +resolved_reference = "d6b54079b123c7f9ca7bb66e32f044f5fed576e7" subdirectory = "pay-api" [[package]] @@ -2125,13 +2135,13 @@ pyasn1 = ">=0.4.6,<0.6.0" [[package]] name = "pycodestyle" -version = "2.11.1" +version = "2.12.0" description = "Python style guide checker" optional = false python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, - {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, + {file = "pycodestyle-2.12.0-py2.py3-none-any.whl", hash = "sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4"}, + {file = "pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c"}, ] [[package]] @@ -2203,17 +2213,17 @@ files = [ [[package]] name = "pylint" -version = "3.2.2" +version = "3.2.6" description = "python code static checker" optional = false python-versions = ">=3.8.0" files = [ - {file = "pylint-3.2.2-py3-none-any.whl", hash = "sha256:3f8788ab20bb8383e06dd2233e50f8e08949cfd9574804564803441a4946eab4"}, - {file = "pylint-3.2.2.tar.gz", hash = "sha256:d068ca1dfd735fb92a07d33cb8f288adc0f6bc1287a139ca2425366f7cbe38f8"}, + {file = "pylint-3.2.6-py3-none-any.whl", hash = "sha256:03c8e3baa1d9fb995b12c1dbe00aa6c4bcef210c2a2634374aedeb22fb4a8f8f"}, + {file = "pylint-3.2.6.tar.gz", hash = "sha256:a5d01678349454806cff6d886fb072294f56a58c4761278c97fb557d708e1eb3"}, ] [package.dependencies] -astroid = ">=3.2.2,<=3.3.0-dev0" +astroid = ">=3.2.4,<=3.3.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = {version = ">=0.3.7", markers = "python_version >= \"3.12\""} isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" @@ -2323,20 +2333,20 @@ files = [ [[package]] name = "pytest" -version = "8.2.2" +version = "8.3.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, - {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, + {file = "pytest-8.3.1-py3-none-any.whl", hash = "sha256:e9600ccf4f563976e2c99fa02c7624ab938296551f280835ee6516df8bc4ae8c"}, + {file = "pytest-8.3.1.tar.gz", hash = "sha256:7e8e5c5abd6e93cb1cc151f23e57adc31fcf8cfd2a3ff2da63e23f732de35db6"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" +pluggy = ">=1.5,<2" [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] @@ -2538,38 +2548,47 @@ files = [ [[package]] name = "sentry-sdk" -version = "1.41.0" +version = "2.13.0" description = "Python client for Sentry (https://sentry.io)" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "sentry-sdk-1.41.0.tar.gz", hash = "sha256:4f2d6c43c07925d8cd10dfbd0970ea7cb784f70e79523cca9dbcd72df38e5a46"}, - {file = "sentry_sdk-1.41.0-py2.py3-none-any.whl", hash = "sha256:be4f8f4b29a80b6a3b71f0f31487beb9e296391da20af8504498a328befed53f"}, + {file = "sentry_sdk-2.13.0-py2.py3-none-any.whl", hash = "sha256:6beede8fc2ab4043da7f69d95534e320944690680dd9a963178a49de71d726c6"}, + {file = "sentry_sdk-2.13.0.tar.gz", hash = "sha256:8d4a576f7a98eb2fdb40e13106e41f330e5c79d72a68be1316e7852cf4995260"}, ] [package.dependencies] +blinker = {version = ">=1.1", optional = true, markers = "extra == \"flask\""} certifi = "*" -urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} +flask = {version = ">=0.11", optional = true, markers = "extra == \"flask\""} +markupsafe = {version = "*", optional = true, markers = "extra == \"flask\""} +urllib3 = ">=1.26.11" [package.extras] aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] arq = ["arq (>=0.23)"] asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] chalice = ["chalice (>=1.16.0)"] clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] -grpcio = ["grpcio (>=1.21.1)"] +grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] +huggingface-hub = ["huggingface-hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] +litestar = ["litestar (>=2.0.0)"] loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] -opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +opentelemetry-experimental = ["opentelemetry-distro"] pure-eval = ["asttokens", "executing", "pure-eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] @@ -2579,22 +2598,23 @@ sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] -tornado = ["tornado (>=5)"] +tornado = ["tornado (>=6)"] [[package]] name = "setuptools" -version = "70.0.0" +version = "73.0.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, - {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, + {file = "setuptools-73.0.1-py3-none-any.whl", hash = "sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e"}, + {file = "setuptools-73.0.1.tar.gz", hash = "sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "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] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.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-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] [[package]] name = "simple-cloudevent" @@ -2647,10 +2667,10 @@ develop = false [package.source] type = "git" -url = "https://github.com/bcgov/lear.git" -reference = "feature-legal-name" -resolved_reference = "e5a432d1460dc84208465ef35c0c81ab02e66f51" -subdirectory = "python/common/sql-versioning" +url = "https://github.com/bcgov/sbc-connect-common.git" +reference = "main" +resolved_reference = "c898988d239dc261b2b186465a1887f15512c102" +subdirectory = "python/sql-versioning" [[package]] name = "sqlalchemy" @@ -2811,33 +2831,33 @@ twisted = ["twisted"] [[package]] name = "tomlkit" -version = "0.12.5" +version = "0.13.0" description = "Style preserving TOML library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"}, - {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, + {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, + {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, ] [[package]] name = "tornado" -version = "6.4" +version = "6.4.1" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false -python-versions = ">= 3.8" +python-versions = ">=3.8" files = [ - {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, - {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"}, - {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"}, - {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, - {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, ] [[package]] @@ -2870,13 +2890,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "werkzeug" -version = "3.0.1" +version = "3.0.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, - {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, + {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, + {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, ] [package.dependencies] @@ -3008,4 +3028,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "c5867c91e7d9de79100f152087e9fcb3f626d37391cf238855a23af0067989dd" +content-hash = "38bc83b0ef6e49b3daf7158017b8b348365ee00bd72642608bccb6f20bc2f728" diff --git a/pay-admin/pyproject.toml b/pay-admin/pyproject.toml index f8a3aaff4..7d325556e 100644 --- a/pay-admin/pyproject.toml +++ b/pay-admin/pyproject.toml @@ -7,23 +7,18 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.12" -gunicorn = "^21.2.0" flask = "^3.0.2" flask-admin = "^1.6.1" flask-oidc = "^2.1.1" -flask-sqlalchemy = "^3.1.1" httplib2 = "^0.22.0" -psycopg2-binary = "^2.9.9" python-dotenv = "^1.0.1" requests = "^2.31.0" requests-futures = "^1.0.1" wtforms = "^3.1.2" werkzeug = "^3.0.1" -sqlalchemy = "^2.0.28" itsdangerous = "^2.1.2" jinja2 = "^3.1.3" -pay-api = {git = "https://github.com/bcgov/sbc-pay.git", subdirectory = "pay-api"} -pg8000 = "^1.30.5" +pay-api = {git = "https://github.com/bcgov/sbc-pay.git", branch = "main", subdirectory = "pay-api"} flask-session = {extras = ["filesystem"], version = "^0.8.0"} diff --git a/pay-admin/setup.cfg b/pay-admin/setup.cfg index fd41bb9f4..a75752031 100755 --- a/pay-admin/setup.cfg +++ b/pay-admin/setup.cfg @@ -45,7 +45,7 @@ per-file-ignores = max_line_length = 120 ignore = E501 docstring-min-length=10 -notes=FIXME,XXX # TODO is ignored +notes=FIXME,XXX match_dir = admin ignored-modules=flask_sqlalchemy sqlalchemy diff --git a/pay-admin/wsgi.py b/pay-admin/wsgi.py index 6142d9182..e12a72ee3 100755 --- a/pay-admin/wsgi.py +++ b/pay-admin/wsgi.py @@ -24,4 +24,4 @@ port = '8080' if len(sys.argv) > 1: port = sys.argv[1] - application.run(port=int(port), debug=True) + application.run(port=int(port)) diff --git a/pay-api/Dockerfile b/pay-api/Dockerfile index cf3e9d235..5bb31156d 100644 --- a/pay-api/Dockerfile +++ b/pay-api/Dockerfile @@ -72,12 +72,13 @@ COPY --chown=web:web ./README.md /code RUN --mount=type=cache,target="$POETRY_CACHE_DIR" \ echo "$APP_ENV" \ && poetry version \ + && poetry config installer.max-workers 1 \ # Install deps: && poetry run pip install -U pip \ && poetry install \ $(if [ -z ${APP_ENV+x} ] | [ "$APP_ENV" = 'production' ]; then echo '--only main'; fi) \ --no-interaction --no-ansi - + # Running as non-root user: USER web diff --git a/pay-api/Makefile b/pay-api/Makefile index 28d32cd32..dbda2894c 100755 --- a/pay-api/Makefile +++ b/pay-api/Makefile @@ -37,8 +37,8 @@ clean-test: ## clean test files rm -fr htmlcov/ install: clean - unset HOME ## unset HOME because it's in the DEV .env file, will cause permissions issues pip install poetry ;\ + poetry config installer.max-workers 1 poetry install ################################################################################# diff --git a/pay-api/gunicorn_config.py b/pay-api/gunicorn_config.py index 484760c33..cd26f3e2f 100755 --- a/pay-api/gunicorn_config.py +++ b/pay-api/gunicorn_config.py @@ -18,10 +18,10 @@ import os # https://docs.gunicorn.org/en/stable/settings.html#workers -workers = int(os.environ.get('GUNICORN_PROCESSES', '3')) # gunicorn default - 1 +workers = int(os.environ.get('GUNICORN_PROCESSES', '1')) # gunicorn default - 1 worker_class = os.environ.get('GUNICORN_WORKER_CLASS', 'sync') # gunicorn default - sync worker_connections = int(os.environ.get('GUNICORN_WORKER_CONNECIONS', '1000')) # gunicorn default - 1000 -threads = int(os.environ.get('GUNICORN_THREADS', '1')) # gunicorn default - 1 +threads = int(os.environ.get('GUNICORN_THREADS', '3')) # gunicorn default - 1 timeout = int(os.environ.get('GUNICORN_TIMEOUT', '100')) # gunicorn default - 30 keepalive = int(os.environ.get('GUNICORN_KEEPALIVE', '2')) # gunicorn default - 2 # WHEN MIGRATING TO GCP - GUNICORN_THREADS = 8, GUNICORN_TIMEOUT = 0, GUNICORN_PROCESSES = 1 diff --git a/pay-api/migrations/README.md b/pay-api/migrations/README.md index a55983d6b..e8b630654 100644 --- a/pay-api/migrations/README.md +++ b/pay-api/migrations/README.md @@ -1,6 +1,6 @@ ## Migrating Pay-db -Run `python manage.py db migrate` +Run `poetry run flask db migrate` If you are updating a large table (i.e. invoices, invoice_references, etc.) add `op.execute(op.text("set statement_timeout=20000;")` to the top of your new migration scripts for upgrade/downgrade. This will prevent the deployment from causing errors in prod when it takes too long to complete (> 20 seconds).) diff --git a/pay-api/migrations/alembic.ini b/pay-api/migrations/alembic.ini index 0ced4d4a6..fa72486db 100644 --- a/pay-api/migrations/alembic.ini +++ b/pay-api/migrations/alembic.ini @@ -4,12 +4,12 @@ script_location = . # template used to generate migration files file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(rev)s_%%(slug)s - +# Will remove these after we confirm the tables are working as expected on PROD. +exclude_tables = account_fees_version,activity,payment_accounts_version,refunds_partial_version,distribution_codes_version,eft_short_names_version,transaction,cfs_accounts_version # set to 'true' to run the environment during # the 'revision' command, regardless of autogenerate # revision_environment = false - # Logging configuration [loggers] keys = root,sqlalchemy,alembic,flask_migrate diff --git a/pay-api/migrations/env.py b/pay-api/migrations/env.py index e6603a9b4..c5e65e04a 100644 --- a/pay-api/migrations/env.py +++ b/pay-api/migrations/env.py @@ -1,6 +1,7 @@ from __future__ import with_statement import logging +import re from logging.config import fileConfig from alembic import context @@ -30,6 +31,18 @@ # my_important_option = config.get_main_option("my_important_option") # ... etc. +def get_list_from_config(config, key): + arr = config.get_main_option(key, []) + if arr: + # split on newlines and commas, then trim (I mean strip) + arr = [token for a in arr.split('\n') for b in a.split(',') if (token := b.strip())] + return arr + + +exclude_tables = get_list_from_config(config, "exclude_tables") + +def include_object(object, name, type_, reflected, compare_to): + return not (type_ == 'table' and name in exclude_tables) def run_migrations_offline(): """Run migrations in 'offline' mode. @@ -45,7 +58,7 @@ def run_migrations_offline(): """ url = config.get_main_option("sqlalchemy.url") context.configure( - url=url, target_metadata=target_metadata, literal_binds=True, compare_type=True + url=url, target_metadata=target_metadata, literal_binds=True, compare_type=True, include_object=include_object ) with context.begin_transaction(): @@ -81,6 +94,7 @@ def process_revision_directives(context, revision, directives): connection=connection, target_metadata=target_metadata, process_revision_directives=process_revision_directives, + include_object=include_object, **current_app.extensions['migrate'].configure_args ) diff --git a/pay-api/migrations/script.py.mako b/pay-api/migrations/script.py.mako index 2c0156303..15d4ac057 100644 --- a/pay-api/migrations/script.py.mako +++ b/pay-api/migrations/script.py.mako @@ -10,6 +10,10 @@ import sqlalchemy as sa ${imports if imports else ""} # revision identifiers, used by Alembic. +# Note you may see foreign keys with distribution_codes_history +# For disbursement_distribution_code_id, service_fee_distribution_code_id +# Please ignore those lines and don't include in migration. + revision = ${repr(up_revision)} down_revision = ${repr(down_revision)} branch_labels = ${repr(branch_labels)} diff --git a/pay-api/migrations/versions/00467a306afd_bcorp_filing_types.py b/pay-api/migrations/versions/00467a306afd_bcorp_filing_types.py index 901b1b098..9a796f0bf 100644 --- a/pay-api/migrations/versions/00467a306afd_bcorp_filing_types.py +++ b/pay-api/migrations/versions/00467a306afd_bcorp_filing_types.py @@ -5,7 +5,7 @@ Create Date: 2019-12-16 09:08:29.440422 """ -from datetime import date +from datetime import datetime, timezone import sqlalchemy as sa from alembic import op @@ -89,7 +89,7 @@ def upgrade(): "filing_type_code": "BCANN", "corp_type_code": "BC", "fee_code": "EN108", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -98,7 +98,7 @@ def upgrade(): "filing_type_code": "BCADD", "corp_type_code": "BC", "fee_code": "EN101", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -107,7 +107,7 @@ def upgrade(): "filing_type_code": "BCCDR", "corp_type_code": "BC", "fee_code": "EN101", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -116,7 +116,7 @@ def upgrade(): "filing_type_code": "BCINC", "corp_type_code": "BC", "fee_code": "EN109", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": "FUT01", "priority_fee_code": None, @@ -125,7 +125,7 @@ def upgrade(): "filing_type_code": "BCREG", "corp_type_code": "BC", "fee_code": "EN109", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -134,7 +134,7 @@ def upgrade(): "filing_type_code": "BCCGM", "corp_type_code": "BC", "fee_code": "EN107", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -143,7 +143,7 @@ def upgrade(): "filing_type_code": "BCRSC", "corp_type_code": "BC", "fee_code": "EN101", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": "PRI01", @@ -152,7 +152,7 @@ def upgrade(): "filing_type_code": "BCFDR", "corp_type_code": "BC", "fee_code": "EN107", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -161,7 +161,7 @@ def upgrade(): "filing_type_code": "BCAMR", "corp_type_code": "BC", "fee_code": "EN109", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": "FUT01", "priority_fee_code": "PRI01", @@ -170,7 +170,7 @@ def upgrade(): "filing_type_code": "BCAMH", "corp_type_code": "BC", "fee_code": "EN109", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": "FUT01", "priority_fee_code": "PRI01", @@ -179,7 +179,7 @@ def upgrade(): "filing_type_code": "BCAMV", "corp_type_code": "BC", "fee_code": "EN109", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": "FUT01", "priority_fee_code": "PRI01", @@ -188,7 +188,7 @@ def upgrade(): "filing_type_code": "BCRSF", "corp_type_code": "BC", "fee_code": "EN109", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": "PRI01", @@ -197,7 +197,7 @@ def upgrade(): "filing_type_code": "BCRSL", "corp_type_code": "BC", "fee_code": "EN109", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": "PRI01", @@ -206,7 +206,7 @@ def upgrade(): "filing_type_code": "BCRSX", "corp_type_code": "BC", "fee_code": "EN109", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": "PRI01", diff --git a/pay-api/migrations/versions/06f6e75c18d8_vital_statistics_fee_code.py b/pay-api/migrations/versions/06f6e75c18d8_vital_statistics_fee_code.py index 76ad74919..6bd4ae676 100644 --- a/pay-api/migrations/versions/06f6e75c18d8_vital_statistics_fee_code.py +++ b/pay-api/migrations/versions/06f6e75c18d8_vital_statistics_fee_code.py @@ -5,7 +5,7 @@ Create Date: 2020-05-25 16:38:23.388619 """ -from datetime import date +from datetime import datetime, timezone import sqlalchemy as sa from alembic import op @@ -83,7 +83,7 @@ def upgrade(): "filing_type_code": "WILLNOTICE", "corp_type_code": "VS", "fee_code": "EN201", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": "PRI02" @@ -92,7 +92,7 @@ def upgrade(): "filing_type_code": "WILLSEARCH", "corp_type_code": "VS", "fee_code": "EN202", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": "PRI02", @@ -101,7 +101,7 @@ def upgrade(): "filing_type_code": "WILLALIAS", "corp_type_code": "VS", "fee_code": "EN203", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": "PRI02", diff --git a/pay-api/migrations/versions/0aa7d4deef8a_nofee_filing_type.py b/pay-api/migrations/versions/0aa7d4deef8a_nofee_filing_type.py index f18d91068..f7c235c13 100644 --- a/pay-api/migrations/versions/0aa7d4deef8a_nofee_filing_type.py +++ b/pay-api/migrations/versions/0aa7d4deef8a_nofee_filing_type.py @@ -5,7 +5,7 @@ Create Date: 2021-05-17 13:15:02.260765 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, String @@ -51,7 +51,7 @@ def upgrade(): "filing_type_code": "NOFEE", "corp_type_code": "BC", "fee_code": "EN107", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -60,7 +60,7 @@ def upgrade(): "filing_type_code": "NOFEE", "corp_type_code": "CP", "fee_code": "EN107", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -69,7 +69,7 @@ def upgrade(): "filing_type_code": "NOFEE", "corp_type_code": "BEN", "fee_code": "EN107", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -78,7 +78,7 @@ def upgrade(): "filing_type_code": "NOFEE", "corp_type_code": "ULC", "fee_code": "EN107", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, diff --git a/pay-api/migrations/versions/2024_07_12_f921d5e32835_add_expense_authority_email.py b/pay-api/migrations/versions/2024_07_12_f921d5e32835_add_expense_authority_email.py index e92c9ac81..12dfe4721 100644 --- a/pay-api/migrations/versions/2024_07_12_f921d5e32835_add_expense_authority_email.py +++ b/pay-api/migrations/versions/2024_07_12_f921d5e32835_add_expense_authority_email.py @@ -21,7 +21,7 @@ def upgrade(): sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('first_name', sa.String(25), nullable=True), sa.Column('last_name', sa.String(25), nullable=True), - sa.Column('email', sa.String(25), nullable=False), + sa.Column('email', sa.String(25), nullable=False) ) def downgrade(): op.drop_table('eft_refund_email_list') diff --git a/pay-api/migrations/versions/2024_07_22_f9c15c7f29f5_.py b/pay-api/migrations/versions/2024_07_22_f9c15c7f29f5_.py new file mode 100644 index 000000000..6b69d478f --- /dev/null +++ b/pay-api/migrations/versions/2024_07_22_f9c15c7f29f5_.py @@ -0,0 +1,46 @@ +"""add primary key constraint to eft_refund_email_list table + +Revision ID: f9c15c7f29f5 +Revises: fb59bf68146d +Create Date: 2024-07-22 14:52:01.050844 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql +from sqlalchemy.schema import Sequence, CreateSequence + +# revision identifiers, used by Alembic. +revision = 'f9c15c7f29f5' +down_revision = 'fb59bf68146d' +branch_labels = None +depends_on = None + + +def upgrade(): + op.drop_table('eft_refund_email_list') + + op.execute(CreateSequence(Sequence('eft_refund_email_list_id_seq'))) + + op.create_table('eft_refund_email_list', + sa.Column('id', sa.Integer(), + sa.Sequence('eft_refund_email_list_id_seq'), + server_default=sa.text("nextval('eft_refund_email_list_id_seq'::regclass)"), + nullable=False), + sa.Column('first_name', sa.String(25), nullable=True), + sa.Column('last_name', sa.String(25), nullable=True), + sa.Column('email', sa.String(25), nullable=False), + sa.PrimaryKeyConstraint('id') + ) +def downgrade(): + op.drop_table('eft_refund_email_list') + + op.execute('DROP SEQUENCE eft_refund_email_list_id_seq') + + op.create_table('eft_refund_email_list', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('first_name', sa.String(25), nullable=True), + sa.Column('last_name', sa.String(25), nullable=True), + sa.Column('email', sa.String(25), nullable=False), + sa.PrimaryKeyConstraint('id') + ) diff --git a/pay-api/migrations/versions/2024_07_25_4e57f6cf649c_.py b/pay-api/migrations/versions/2024_07_25_4e57f6cf649c_.py new file mode 100644 index 000000000..0cb25159d --- /dev/null +++ b/pay-api/migrations/versions/2024_07_25_4e57f6cf649c_.py @@ -0,0 +1,93 @@ +""" + +Revision ID: 4e57f6cf649c +Revises: f9c15c7f29f5 +Create Date: 2024-07-25 16:03:51.968355 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4e57f6cf649c' +down_revision = 'f9c15c7f29f5' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.execute('DROP SEQUENCE IF EXISTS eft_refund_email_list_id_seq CASCADE') + op.execute('DROP TABLE IF EXISTS eft_refund_email_list') + op.create_table('eft_refund_email_list', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('first_name', sa.String(25), nullable=True), + sa.Column('last_name', sa.String(25), nullable=True), + sa.Column('email', sa.String(25), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + + with op.batch_alter_table('eft_credit_invoice_links', schema=None) as batch_op: + batch_op.alter_column('status_code', + existing_type=sa.VARCHAR(length=25), + nullable=False) + + with op.batch_alter_table('eft_refunds', schema=None) as batch_op: + batch_op.alter_column('comment', + existing_type=sa.VARCHAR(), + nullable=False) + + with op.batch_alter_table('ejv_links', schema=None) as batch_op: + batch_op.alter_column('link_type', + existing_type=sa.VARCHAR(length=20), + type_=sa.String(length=50), + existing_nullable=True) + batch_op.drop_index('ix_ejv_invoice_links_ejv_header_id') + batch_op.drop_index('ix_ejv_links_link_type_link_id') + batch_op.create_index(batch_op.f('ix_ejv_links_ejv_header_id'), ['ejv_header_id'], unique=False) + batch_op.create_index(batch_op.f('ix_ejv_links_link_id'), ['link_id'], unique=False) + batch_op.create_index(batch_op.f('ix_ejv_links_link_type'), ['link_type'], unique=False) + + with op.batch_alter_table('refunds_partial', schema=None) as batch_op: + batch_op.alter_column('disbursement_date', + existing_type=sa.DATE(), + type_=sa.DateTime(), + existing_nullable=True) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('refunds_partial', schema=None) as batch_op: + batch_op.alter_column('disbursement_date', + existing_type=sa.DateTime(), + type_=sa.DATE(), + existing_nullable=True) + + with op.batch_alter_table('ejv_links', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_ejv_links_link_type')) + batch_op.drop_index(batch_op.f('ix_ejv_links_link_id')) + batch_op.drop_index(batch_op.f('ix_ejv_links_ejv_header_id')) + batch_op.create_index('ix_ejv_links_link_type_link_id', ['link_type', 'link_id'], unique=False) + batch_op.create_index('ix_ejv_invoice_links_ejv_header_id', ['ejv_header_id'], unique=False) + batch_op.alter_column('link_type', + existing_type=sa.String(length=50), + type_=sa.VARCHAR(length=20), + existing_nullable=True) + + with op.batch_alter_table('eft_refunds', schema=None) as batch_op: + batch_op.alter_column('comment', + existing_type=sa.VARCHAR(), + nullable=True) + + with op.batch_alter_table('eft_credit_invoice_links', schema=None) as batch_op: + batch_op.alter_column('status_code', + existing_type=sa.VARCHAR(length=25), + nullable=True) + + with op.batch_alter_table('distribution_codes_history', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.drop_constraint(None, type_='foreignkey') + # ### end Alembic commands ### diff --git a/pay-api/migrations/versions/2024_07_30_1d5b66ef7f81_overdue_notification_date.py b/pay-api/migrations/versions/2024_07_30_1d5b66ef7f81_overdue_notification_date.py new file mode 100644 index 000000000..19764de9c --- /dev/null +++ b/pay-api/migrations/versions/2024_07_30_1d5b66ef7f81_overdue_notification_date.py @@ -0,0 +1,30 @@ +"""Add in overdue notification date, for debugging also so we don't send multiples. + +Revision ID: 1d5b66ef7f81 +Revises: 4e57f6cf649c +Create Date: 2024-07-30 14:47:23.187621 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +# Note you may see foreign keys with distribution_codes_history +# For disbursement_distribution_code_id, service_fee_distribution_code_id +# Please ignore those lines and don't include in migration. + +revision = '1d5b66ef7f81' +down_revision = '4e57f6cf649c' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute("set statement_timeout=60000;") + with op.batch_alter_table('statements', schema=None) as batch_op: + batch_op.add_column(sa.Column('overdue_notification_date', sa.Date(), nullable=True)) + +def downgrade(): + with op.batch_alter_table('statements', schema=None) as batch_op: + batch_op.drop_column('overdue_notification_date') diff --git a/pay-api/migrations/versions/2024_08_06_e64c153e63ae_eft_short_names_historical.py b/pay-api/migrations/versions/2024_08_06_e64c153e63ae_eft_short_names_historical.py new file mode 100644 index 000000000..0b27937e4 --- /dev/null +++ b/pay-api/migrations/versions/2024_08_06_e64c153e63ae_eft_short_names_historical.py @@ -0,0 +1,76 @@ +"""Table to track EFT Short name historical payment activities. + +Revision ID: e64c153e63ae +Revises: f9c15c7f29f5 +Create Date: 2024-08-06 13:27:04.018005 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = 'e64c153e63ae' +down_revision = '1d5b66ef7f81' +branch_labels = None +depends_on = None + + +def upgrade(): + # EFT Credit Invoice Group Link sequence created outside for a table so the same value can be used for + # multiple records + op.execute(sa.schema.CreateSequence(sa.Sequence('eft_group_link_seq', data_type=sa.Integer))) + + with op.batch_alter_table('eft_credit_invoice_links', schema=None) as batch_op: + batch_op.add_column(sa.Column('link_group_id', sa.Integer(), nullable=True)) + + op.create_table('eft_short_names_historical', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('amount', sa.Numeric(precision=19, scale=2), nullable=False), + sa.Column('created_by', sa.String(), nullable=True), + sa.Column('created_on', sa.DateTime(), nullable=False), + sa.Column('credit_balance', sa.Numeric(precision=19, scale=2), nullable=False), + sa.Column('hidden', sa.Boolean(), nullable=False), + sa.Column('is_processing', sa.Boolean(), nullable=False), + sa.Column('payment_account_id', sa.Integer(), nullable=True), + sa.Column('related_group_link_id', sa.Integer(), nullable=True), + sa.Column('short_name_id', sa.Integer(), nullable=False), + sa.Column('statement_number', sa.Integer(), nullable=True), + sa.Column('transaction_date', sa.DateTime(), nullable=False), + sa.Column('transaction_type', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['payment_account_id'], ['payment_accounts.id'], ), + sa.ForeignKeyConstraint(['short_name_id'], ['eft_short_names.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('eft_short_names_historical', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_eft_short_names_historical_hidden'), ['hidden'], unique=False) + batch_op.create_index(batch_op.f('ix_eft_short_names_historical_payment_account_id'), ['payment_account_id'], unique=False) + batch_op.create_index(batch_op.f('ix_eft_short_names_historical_related_group_link_id'), ['related_group_link_id'], unique=False) + batch_op.create_index(batch_op.f('ix_eft_short_names_historical_transaction_date'), ['transaction_date'], unique=False) + + with op.batch_alter_table('eft_credits', schema=None) as batch_op: + batch_op.drop_index('ix_eft_credits_payment_account_id') + batch_op.drop_constraint('eft_credits_payment_account_id_fkey', type_='foreignkey') + batch_op.drop_column('payment_account_id') + + +def downgrade(): + with op.batch_alter_table('eft_short_names_historical', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_eft_short_names_historical_transaction_date')) + batch_op.drop_index(batch_op.f('ix_eft_short_names_historical_related_group_link_id')) + batch_op.drop_index(batch_op.f('ix_eft_short_names_historical_payment_account_id')) + batch_op.drop_index(batch_op.f('ix_eft_short_names_historical_hidden')) + + op.drop_table('eft_short_names_historical') + + with op.batch_alter_table('eft_credit_invoice_links', schema=None) as batch_op: + batch_op.alter_column('status_code', + existing_type=sa.VARCHAR(length=25), + nullable=True) + batch_op.drop_column('link_group_id') + + op.execute('DROP SEQUENCE eft_group_link_seq') + + with op.batch_alter_table('eft_credits', schema=None) as batch_op: + batch_op.add_column(sa.Column('payment_account_id', sa.INTEGER(), autoincrement=False, nullable=True)) + batch_op.create_foreign_key('eft_credits_payment_account_id_fkey', 'payment_accounts', ['payment_account_id'], ['id']) + batch_op.create_index('ix_eft_credits_payment_account_id', ['payment_account_id'], unique=False) diff --git a/pay-api/migrations/versions/2024_08_07_d197b43e25dc_eft_created_to_approved.py b/pay-api/migrations/versions/2024_08_07_d197b43e25dc_eft_created_to_approved.py new file mode 100644 index 000000000..3eb5c4291 --- /dev/null +++ b/pay-api/migrations/versions/2024_08_07_d197b43e25dc_eft_created_to_approved.py @@ -0,0 +1,27 @@ +"""Move EFT invoices from created to APPROVED. + +Revision ID: d197b43e25dc +Revises: e64c153e63ae +Create Date: 2024-08-07 11:49:14.975144 + +""" +from alembic import op +from pay_api.utils.enums import InvoiceStatus + +# revision identifiers, used by Alembic. +# Note you may see foreign keys with distribution_codes_history +# For disbursement_distribution_code_id, service_fee_distribution_code_id +# Please ignore those lines and don't include in migration. + +revision = 'd197b43e25dc' +down_revision = 'e64c153e63ae' +branch_labels = None +depends_on = None + +def upgrade(): + op.execute(f"update invoices set invoice_status_code = '{InvoiceStatus.APPROVED.value}' where invoice_status_code = '{InvoiceStatus.CREATED.value}' and payment_method_code = 'EFT'") + op.execute(f"update invoice_status_codes set description = 'Invoice Approved' where code = '{InvoiceStatus.APPROVED.value}'") + +def downgrade(): + op.execute(f"update invoices set invoice_status_code = '{InvoiceStatus.CREATED.value}' where invoice_status_code = '{InvoiceStatus.APPROVED.value}' and payment_method_code = 'EFT'") + op.execute(f"update invoice_status_codes set description = 'PAD Invoice Approved' where code = '{InvoiceStatus.APPROVED.value}'") diff --git a/pay-api/migrations/versions/2024_08_08_17ca5cd561ca_.py b/pay-api/migrations/versions/2024_08_08_17ca5cd561ca_.py new file mode 100644 index 000000000..a47407f7a --- /dev/null +++ b/pay-api/migrations/versions/2024_08_08_17ca5cd561ca_.py @@ -0,0 +1,38 @@ +"""Create indexes to optimize amount owing. + +Revision ID: 17ca5cd561ca +Revises: d197b43e25dc +Create Date: 2024-08-08 11:31:56.857656 + +""" +from alembic import op + +# revision identifiers, used by Alembic. +# Note you may see foreign keys with distribution_codes_history +# For disbursement_distribution_code_id, service_fee_distribution_code_id +# Please ignore those lines and don't include in migration. + +revision = '17ca5cd561ca' +down_revision = 'd197b43e25dc' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute("set statement_timeout=900000;") + with op.batch_alter_table('invoices', schema=None) as batch_op: + # existing adhoc on PROD + op.execute('DROP INDEX IF EXISTS invoices_invoice_status_code_idx;') + batch_op.create_index(batch_op.f('ix_invoices_invoice_status_code'), ['invoice_status_code'], unique=False) + + with op.batch_alter_table('statement_invoices', schema=None) as batch_op: + # existing adhoc on PROD + op.execute('DROP INDEX IF EXISTS statement_invoices_invoice_id_idx;') + batch_op.create_index(batch_op.f('ix_statement_invoices_invoice_id'), ['invoice_id'], unique=False) + +def downgrade(): + with op.batch_alter_table('statement_invoices', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_statement_invoices_invoice_id')) + + with op.batch_alter_table('invoices', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_invoices_invoice_status_code')) diff --git a/pay-api/migrations/versions/2024_08_08_5cb9c5f5896c_.py b/pay-api/migrations/versions/2024_08_08_5cb9c5f5896c_.py new file mode 100644 index 000000000..e15609e23 --- /dev/null +++ b/pay-api/migrations/versions/2024_08_08_5cb9c5f5896c_.py @@ -0,0 +1,67 @@ +"""Add in payment methods string column, this way we don't need to look at payment method history and we don't + have bad performance looking at statements invoices. + +Revision ID: 5cb9c5f5896c +Revises: 17ca5cd561ca +Create Date: 2024-08-08 22:01:40.129963 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +# Note you may see foreign keys with distribution_codes_history +# For disbursement_distribution_code_id, service_fee_distribution_code_id +# Please ignore those lines and don't include in migration. + +revision = '5cb9c5f5896c' +down_revision = '17ca5cd561ca' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute("set statement_timeout=900000;") + with op.batch_alter_table('statements', schema=None) as batch_op: + batch_op.add_column(sa.Column('payment_methods', sa.String(length=100), nullable=True)) + + # Scenario where there are no statement invoices. + # Note this takes at least 15 minutes to run both queries. + op.execute(""" + update + statements + set + (payment_methods) = ( + select + payment_method + from + payment_accounts + where + payment_accounts.id = statements.payment_account_id + ) + where payment_methods is null and not exists (select 1 from statement_invoices where statement_invoices.statement_id = statements.id); + """) + + # Scenario where there exists statement invoices. + op.execute(""" + update + statements + set + (payment_methods) = ( + select + string_agg(distinct payment_method_code, ',') + from + statement_invoices + join invoices on + invoices.id = statement_invoices.invoice_id + where + statement_invoices.statement_id = statements.id + group by statement_invoices.statement_id + ) + where payment_methods is null; + """) + +def downgrade(): + with op.batch_alter_table('statements', schema=None) as batch_op: + batch_op.drop_column('payment_methods') diff --git a/pay-api/migrations/versions/2024_08_14_4410b7fc6437_eft_invoice_refund_historical.py b/pay-api/migrations/versions/2024_08_14_4410b7fc6437_eft_invoice_refund_historical.py new file mode 100644 index 000000000..f6b1da2dd --- /dev/null +++ b/pay-api/migrations/versions/2024_08_14_4410b7fc6437_eft_invoice_refund_historical.py @@ -0,0 +1,34 @@ +"""Invoice refund support for eft_short_names_historical + +Revision ID: 4410b7fc6437 +Revises: 5cb9c5f5896c +Create Date: 2024-08-14 10:42:13.484178 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +# Note you may see foreign keys with distribution_codes_history +# For disbursement_distribution_code_id, service_fee_distribution_code_id +# Please ignore those lines and don't include in migration. + +revision = '4410b7fc6437' +down_revision = '5cb9c5f5896c' +branch_labels = None +depends_on = None + + +def upgrade(): + with op.batch_alter_table('eft_short_names_historical', schema=None) as batch_op: + batch_op.add_column(sa.Column('invoice_id', sa.Integer(), nullable=True)) + batch_op.create_index(batch_op.f('ix_eft_short_names_historical_invoice_id'), ['invoice_id'], unique=False) + batch_op.create_foreign_key('eft_short_names_historical_invoice_id_fkey', 'invoices', ['invoice_id'], ['id']) + + +def downgrade(): + with op.batch_alter_table('eft_short_names_historical', schema=None) as batch_op: + batch_op.drop_constraint('eft_short_names_historical_invoice_id_fkey', type_='foreignkey') + batch_op.drop_index(batch_op.f('ix_eft_short_names_historical_invoice_id')) + batch_op.drop_column('invoice_id') diff --git a/pay-api/migrations/versions/2024_08_16_fc32e7db4493_.py b/pay-api/migrations/versions/2024_08_16_fc32e7db4493_.py new file mode 100644 index 000000000..5960f9cf2 --- /dev/null +++ b/pay-api/migrations/versions/2024_08_16_fc32e7db4493_.py @@ -0,0 +1,43 @@ +"""Add in fields we can easily check and return to clients. + +Revision ID: fc32e7db4493 +Revises: 4410b7fc6437 +Create Date: 2024-08-16 12:36:57.797210 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +# Note you may see foreign keys with distribution_codes_history +# For disbursement_distribution_code_id, service_fee_distribution_code_id +# Please ignore those lines and don't include in migration. + +revision = 'fc32e7db4493' +down_revision = '4410b7fc6437' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute("set statement_timeout=900000;") + with op.batch_alter_table('payment_accounts', schema=None) as batch_op: + batch_op.add_column(sa.Column('has_nsf_invoices', sa.DateTime(), nullable=True)) + batch_op.add_column(sa.Column('has_overdue_invoices', sa.DateTime(), nullable=True)) + + with op.batch_alter_table('payment_accounts_history', schema=None) as batch_op: + batch_op.add_column(sa.Column('has_nsf_invoices', sa.DateTime(), autoincrement=False, nullable=True)) + batch_op.add_column(sa.Column('has_overdue_invoices', sa.DateTime(), autoincrement=False, nullable=True)) + + op.execute("update payment_accounts pa set has_nsf_invoices = (select now() from cfs_accounts ca where ca.account_id = pa.id and ca.status = \'FREEZE\' and ca.payment_method = \'PAD\' limit 1)") + op.execute("update payment_accounts pa set has_overdue_invoices = (select now() from invoices i where i.payment_method_code = 'EFT' and i.payment_account_id = pa.id and i.invoice_status_code = \'OVERDUE\' limit 1)") + +def downgrade(): + with op.batch_alter_table('payment_accounts_history', schema=None) as batch_op: + batch_op.drop_column('has_overdue_invoices') + batch_op.drop_column('has_nsf_invoices') + + with op.batch_alter_table('payment_accounts', schema=None) as batch_op: + batch_op.drop_column('has_overdue_invoices') + batch_op.drop_column('has_nsf_invoices') diff --git a/pay-api/migrations/versions/2024_08_29_2097573390f1_.py b/pay-api/migrations/versions/2024_08_29_2097573390f1_.py new file mode 100644 index 000000000..8b0892525 --- /dev/null +++ b/pay-api/migrations/versions/2024_08_29_2097573390f1_.py @@ -0,0 +1,36 @@ +"""Add is_consolidated and created_on columns to invoice_references + +Revision ID: 2097573390f1 +Revises: fc32e7db4493 +Create Date: 2024-08-29 12:01:51.061253 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +# Note you may see foreign keys with distribution_codes_history +# For disbursement_distribution_code_id, service_fee_distribution_code_id +# Please ignore those lines and don't include in migration. + +revision = '2097573390f1' +down_revision = 'fc32e7db4493' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute("set statement_timeout=900000;") + with op.batch_alter_table('invoice_references', schema=None) as batch_op: + batch_op.add_column(sa.Column('created_on', sa.DateTime(), nullable=True)) + batch_op.add_column(sa.Column('is_consolidated', sa.Boolean(), server_default='f', nullable=False)) + batch_op.create_index(batch_op.f('ix_invoice_references_is_consolidated'), ['is_consolidated'], unique=False) + op.execute("update invoice_references set is_consolidated = 't' where invoice_number like '%-C'") + + +def downgrade(): + with op.batch_alter_table('invoice_references', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_invoice_references_is_consolidated')) + batch_op.drop_column('is_consolidated') + batch_op.drop_column('created_on') diff --git a/pay-api/migrations/versions/2024_09_06_aae01971bd53_.py b/pay-api/migrations/versions/2024_09_06_aae01971bd53_.py new file mode 100644 index 000000000..5ee0c1888 --- /dev/null +++ b/pay-api/migrations/versions/2024_09_06_aae01971bd53_.py @@ -0,0 +1,64 @@ +"""Add in has_partner_disbursements column to corp_types, easier for querying, also remove unused columns. +Also add in partner disbursements. + +Revision ID: aae01971bd53 +Revises: 2097573390f1 +Create Date: 2024-09-06 10:51:20.058891 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'aae01971bd53' +down_revision = '2097573390f1' +branch_labels = None +depends_on = None + + +def upgrade(): + with op.batch_alter_table('corp_types', schema=None) as batch_op: + batch_op.add_column(sa.Column('has_partner_disbursements', sa.Boolean(), nullable=True)) + + with op.batch_alter_table('refunds_partial', schema=None) as batch_op: + batch_op.drop_constraint('refunds_partial_disbursement_status_code_fkey', type_='foreignkey') + batch_op.drop_column('disbursement_status_code') + batch_op.drop_column('disbursement_date') + + with op.batch_alter_table('refunds_partial_history', schema=None) as batch_op: + batch_op.drop_constraint('refunds_partial_history_disbursement_status_code_fkey', type_='foreignkey') + batch_op.drop_column('disbursement_status_code') + batch_op.drop_column('disbursement_date') + + op.create_table('partner_disbursements', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('amount', sa.Numeric(), nullable=False), + sa.Column('created_on', sa.DateTime(), nullable=False), + sa.Column('feedback_on', sa.DateTime(), nullable=True), + sa.Column('partner_code', sa.String(length=50), nullable=False), + sa.Column('processed_on', sa.DateTime(), nullable=True), + sa.Column('is_reversal', sa.Boolean(), nullable=False), + sa.Column('status_code', sa.String(length=25), nullable=False), + sa.Column('target_id', sa.Integer(), nullable=True), + sa.Column('target_type', sa.String(length=50), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + +def downgrade(): + op.drop_table('partner_disbursements') + + with op.batch_alter_table('corp_types', schema=None) as batch_op: + batch_op.drop_column('has_partner_disbursements') + + with op.batch_alter_table('refunds_partial', schema=None) as batch_op: + batch_op.add_column(sa.Column('disbursement_status_code', sa.String(length=20), nullable=True)) + batch_op.add_column(sa.Column('disbursement_date', sa.Date(), nullable=True)) + batch_op.create_foreign_key(None, 'refunds_partial', 'disbursement_status_codes', ['disbursement_status_code'], ['code']) + + with op.batch_alter_table('refunds_partial_history', schema=None) as batch_op: + batch_op.add_column(sa.Column('disbursement_status_code', sa.String(length=20), nullable=True)) + batch_op.add_column(sa.Column('disbursement_date', sa.Date(), nullable=True)) + batch_op.create_foreign_key(None, 'refunds_partial_history', 'disbursement_status_codes', ['disbursement_status_code'], ['code']) + + diff --git a/pay-api/migrations/versions/2efd8e2b92a1_ppr_fee_codes.py b/pay-api/migrations/versions/2efd8e2b92a1_ppr_fee_codes.py index 615bb227f..081ee3d90 100644 --- a/pay-api/migrations/versions/2efd8e2b92a1_ppr_fee_codes.py +++ b/pay-api/migrations/versions/2efd8e2b92a1_ppr_fee_codes.py @@ -5,7 +5,7 @@ Create Date: 2021-03-16 10:49:56.185103 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, Float, String @@ -70,7 +70,7 @@ def upgrade(): "filing_type_code": "FLREG", "corp_type_code": "PPR", "fee_code": "EN114", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -80,7 +80,7 @@ def upgrade(): "filing_type_code": "RLREG", "corp_type_code": "PPR", "fee_code": "EN111", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -90,7 +90,7 @@ def upgrade(): "filing_type_code": "FSREN", "corp_type_code": "PPR", "fee_code": "EN111", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -100,7 +100,7 @@ def upgrade(): "filing_type_code": "INFRN", "corp_type_code": "PPR", "fee_code": "EN112", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": "FUT01", "priority_fee_code": None, @@ -110,7 +110,7 @@ def upgrade(): "filing_type_code": "FSCHG", "corp_type_code": "PPR", "fee_code": "EN114", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, diff --git a/pay-api/migrations/versions/44bd57ece7b0_ppr_filing_types.py b/pay-api/migrations/versions/44bd57ece7b0_ppr_filing_types.py index fc517e881..282893bbe 100644 --- a/pay-api/migrations/versions/44bd57ece7b0_ppr_filing_types.py +++ b/pay-api/migrations/versions/44bd57ece7b0_ppr_filing_types.py @@ -7,7 +7,7 @@ """ from alembic import op import sqlalchemy as sa -from datetime import date +from datetime import datetime, timezone import sqlalchemy as sa from alembic import op @@ -51,11 +51,11 @@ def upgrade(): op.bulk_insert( fee_schedule_table, [ - {'filing_type_code': 'FSDIS', 'corp_type_code': 'PPR', 'fee_code': 'EN107', 'fee_start_date': date.today(), + {'filing_type_code': 'FSDIS', 'corp_type_code': 'PPR', 'fee_code': 'EN107', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'NCREG', 'corp_type_code': 'PPR', 'fee_code': 'EN107', 'fee_start_date': date.today(), + {'filing_type_code': 'NCREG', 'corp_type_code': 'PPR', 'fee_code': 'EN107', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'NCCHG', 'corp_type_code': 'PPR', 'fee_code': 'EN107', 'fee_start_date': date.today(), + {'filing_type_code': 'NCCHG', 'corp_type_code': 'PPR', 'fee_code': 'EN107', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None} ] ) diff --git a/pay-api/migrations/versions/4cb0dc8e0013_ppr_and_dissolution_filing_type_codes.py b/pay-api/migrations/versions/4cb0dc8e0013_ppr_and_dissolution_filing_type_codes.py index 4172081c9..851565453 100644 --- a/pay-api/migrations/versions/4cb0dc8e0013_ppr_and_dissolution_filing_type_codes.py +++ b/pay-api/migrations/versions/4cb0dc8e0013_ppr_and_dissolution_filing_type_codes.py @@ -5,7 +5,7 @@ Create Date: 2021-10-06 07:57:39.021398 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, String, text @@ -61,7 +61,7 @@ def upgrade(): 'filing_type_code': 'SSRCH', 'corp_type_code': 'PPR', 'fee_code': 'EN114', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -71,7 +71,7 @@ def upgrade(): 'filing_type_code': 'AFDVT', 'corp_type_code': 'CP', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -81,7 +81,7 @@ def upgrade(): 'filing_type_code': 'SPRLN', 'corp_type_code': 'CP', 'fee_code': 'EN104', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -91,7 +91,7 @@ def upgrade(): 'filing_type_code': 'DIS_VOL', 'corp_type_code': 'CP', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -101,7 +101,7 @@ def upgrade(): 'filing_type_code': 'DIS_LQD', 'corp_type_code': 'CP', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -111,7 +111,7 @@ def upgrade(): 'filing_type_code': 'DIS_VOL', 'corp_type_code': 'BC', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -121,7 +121,7 @@ def upgrade(): 'filing_type_code': 'DIS_INVOL', 'corp_type_code': 'BC', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -131,7 +131,7 @@ def upgrade(): 'filing_type_code': 'DIS_ADMIN', 'corp_type_code': 'BC', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -141,7 +141,7 @@ def upgrade(): 'filing_type_code': 'DIS_LQD', 'corp_type_code': 'BC', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -151,7 +151,7 @@ def upgrade(): 'filing_type_code': 'DIS_COLQD', 'corp_type_code': 'BC', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -161,7 +161,7 @@ def upgrade(): 'filing_type_code': 'DIS_RSTR', 'corp_type_code': 'BC', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -172,7 +172,7 @@ def upgrade(): 'filing_type_code': 'DIS_VOL', 'corp_type_code': 'BEN', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -182,7 +182,7 @@ def upgrade(): 'filing_type_code': 'DIS_INVOL', 'corp_type_code': 'BEN', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -192,7 +192,7 @@ def upgrade(): 'filing_type_code': 'DIS_ADMIN', 'corp_type_code': 'BEN', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -202,7 +202,7 @@ def upgrade(): 'filing_type_code': 'DIS_LQD', 'corp_type_code': 'BEN', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -212,7 +212,7 @@ def upgrade(): 'filing_type_code': 'DIS_COLQD', 'corp_type_code': 'BEN', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -222,7 +222,7 @@ def upgrade(): 'filing_type_code': 'DIS_RSTR', 'corp_type_code': 'BEN', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -233,7 +233,7 @@ def upgrade(): 'filing_type_code': 'DIS_VOL', 'corp_type_code': 'CC', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -243,7 +243,7 @@ def upgrade(): 'filing_type_code': 'DIS_INVOL', 'corp_type_code': 'CC', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -253,7 +253,7 @@ def upgrade(): 'filing_type_code': 'DIS_ADMIN', 'corp_type_code': 'CC', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -263,7 +263,7 @@ def upgrade(): 'filing_type_code': 'DIS_LQD', 'corp_type_code': 'CC', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -273,7 +273,7 @@ def upgrade(): 'filing_type_code': 'DIS_COLQD', 'corp_type_code': 'CC', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -283,7 +283,7 @@ def upgrade(): 'filing_type_code': 'DIS_RSTR', 'corp_type_code': 'CC', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -294,7 +294,7 @@ def upgrade(): 'filing_type_code': 'DIS_VOL', 'corp_type_code': 'ULC', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -304,7 +304,7 @@ def upgrade(): 'filing_type_code': 'DIS_INVOL', 'corp_type_code': 'ULC', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -314,7 +314,7 @@ def upgrade(): 'filing_type_code': 'DIS_ADMIN', 'corp_type_code': 'ULC', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -324,7 +324,7 @@ def upgrade(): 'filing_type_code': 'DIS_LQD', 'corp_type_code': 'ULC', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -334,7 +334,7 @@ def upgrade(): 'filing_type_code': 'DIS_COLQD', 'corp_type_code': 'ULC', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -344,7 +344,7 @@ def upgrade(): 'filing_type_code': 'DIS_RSTR', 'corp_type_code': 'ULC', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -355,7 +355,7 @@ def upgrade(): 'filing_type_code': 'DIS_INVOL', 'corp_type_code': 'LTD', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -365,7 +365,7 @@ def upgrade(): 'filing_type_code': 'DIS_ADMIN', 'corp_type_code': 'LTD', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -375,7 +375,7 @@ def upgrade(): 'filing_type_code': 'DIS_LQD', 'corp_type_code': 'LTD', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -385,7 +385,7 @@ def upgrade(): 'filing_type_code': 'DIS_COLQD', 'corp_type_code': 'LTD', 'fee_code': 'EN101', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'future_effective_fee_code': 'FUT01', @@ -395,7 +395,7 @@ def upgrade(): 'filing_type_code': 'DIS_RSTR', 'corp_type_code': 'LTD', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, diff --git a/pay-api/migrations/versions/5cdd1c1d355e_correction_fee_codes.py b/pay-api/migrations/versions/5cdd1c1d355e_correction_fee_codes.py index fce0658c3..69e068a1a 100644 --- a/pay-api/migrations/versions/5cdd1c1d355e_correction_fee_codes.py +++ b/pay-api/migrations/versions/5cdd1c1d355e_correction_fee_codes.py @@ -5,7 +5,7 @@ Create Date: 2020-02-13 13:43:53.035222 """ -from datetime import date +from datetime import datetime, timezone import sqlalchemy as sa from alembic import op @@ -52,7 +52,7 @@ def upgrade(): "filing_type_code": "CRCTN", "corp_type_code": "BC", "fee_code": "EN101", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": "PRI01", @@ -61,7 +61,7 @@ def upgrade(): "filing_type_code": "CRCTN", "corp_type_code": "CP", "fee_code": "EN101", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": "PRI01", diff --git a/pay-api/migrations/versions/5d9997f7e649_variable_fee_code.py b/pay-api/migrations/versions/5d9997f7e649_variable_fee_code.py index ffbe5a240..f0cf4d3d1 100644 --- a/pay-api/migrations/versions/5d9997f7e649_variable_fee_code.py +++ b/pay-api/migrations/versions/5d9997f7e649_variable_fee_code.py @@ -5,7 +5,7 @@ Create Date: 2021-11-22 13:47:57.469953 """ -from datetime import date +from datetime import datetime, timezone import sqlalchemy as sa from alembic import op @@ -76,7 +76,7 @@ def upgrade(): 'filing_type_code': 'CSBVFEE', 'corp_type_code': 'CSO', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'variable': True @@ -85,7 +85,7 @@ def upgrade(): 'filing_type_code': 'CSBSRCH', 'corp_type_code': 'CSO', 'fee_code': 'EN115', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'variable': False @@ -94,7 +94,7 @@ def upgrade(): 'filing_type_code': 'CSBPDOC', 'corp_type_code': 'CSO', 'fee_code': 'EN114', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'variable': False @@ -103,7 +103,7 @@ def upgrade(): 'filing_type_code': 'CSCRMTFC', 'corp_type_code': 'CSO', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'variable': False diff --git a/pay-api/migrations/versions/5f7df60469fa_adding_ppr_metadata.py b/pay-api/migrations/versions/5f7df60469fa_adding_ppr_metadata.py index ba24a09e5..4a40d8b93 100644 --- a/pay-api/migrations/versions/5f7df60469fa_adding_ppr_metadata.py +++ b/pay-api/migrations/versions/5f7df60469fa_adding_ppr_metadata.py @@ -5,7 +5,7 @@ Create Date: 2020-02-28 14:33:27.703812 """ -from datetime import date +from datetime import datetime, timezone import sqlalchemy as sa from alembic import op @@ -80,11 +80,11 @@ def upgrade(): op.bulk_insert( fee_schedule_table, [ - {'filing_type_code': 'SERCH', 'corp_type_code': 'PPR', 'fee_code': 'EN110', 'fee_start_date': date.today(), + {'filing_type_code': 'SERCH', 'corp_type_code': 'PPR', 'fee_code': 'EN110', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'FSREG', 'corp_type_code': 'PPR', 'fee_code': 'EN111', 'fee_start_date': date.today(), + {'filing_type_code': 'FSREG', 'corp_type_code': 'PPR', 'fee_code': 'EN111', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'INFRG', 'corp_type_code': 'PPR', 'fee_code': 'EN112', 'fee_start_date': date.today(), + {'filing_type_code': 'INFRG', 'corp_type_code': 'PPR', 'fee_code': 'EN112', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None} ] ) diff --git a/pay-api/migrations/versions/609b98d87a72_rpt.py b/pay-api/migrations/versions/609b98d87a72_rpt.py index 0f720fdaa..f78bebd59 100644 --- a/pay-api/migrations/versions/609b98d87a72_rpt.py +++ b/pay-api/migrations/versions/609b98d87a72_rpt.py @@ -5,7 +5,7 @@ Create Date: 2021-09-16 11:09:04.691660 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, String, Boolean, text @@ -64,7 +64,7 @@ def upgrade(): 'filing_type_code': 'OLLXTFI', 'corp_type_code': 'RPT', 'fee_code': 'EN111', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01' } diff --git a/pay-api/migrations/versions/643790dd3334_cso.py b/pay-api/migrations/versions/643790dd3334_cso.py index 33957916e..df9a0ea65 100644 --- a/pay-api/migrations/versions/643790dd3334_cso.py +++ b/pay-api/migrations/versions/643790dd3334_cso.py @@ -7,7 +7,7 @@ """ from alembic import op import sqlalchemy as sa -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, String, Boolean @@ -67,7 +67,7 @@ def upgrade(): 'filing_type_code': 'CSBFILE', 'corp_type_code': 'CSO', 'fee_code': 'EN110', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None } diff --git a/pay-api/migrations/versions/6a6b042b831a_.py b/pay-api/migrations/versions/6a6b042b831a_.py index adf1185f5..737f75bb0 100644 --- a/pay-api/migrations/versions/6a6b042b831a_.py +++ b/pay-api/migrations/versions/6a6b042b831a_.py @@ -6,7 +6,7 @@ Create Date: 2022-05-24 21:46:54.030721 """ -from datetime import date +from datetime import datetime, timezone from re import M from alembic import op @@ -88,7 +88,7 @@ def upgrade(): 'filing_type_code': 'BSRCH', 'corp_type_code': 'BUS', 'fee_code': 'EN110', # 7.00 - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', #$1.50 default } @@ -98,14 +98,14 @@ def upgrade(): op.bulk_insert(distribution_code_table, [ { - 'created_on': date.today(), + 'created_on': datetime.now(tz=timezone.utc), 'name': 'Corporate Registry - Searches', 'client': '112', 'responsibility_centre': '32363', 'service_line': '34725', 'stob': '4375', 'project_code': '3200056', - 'start_date': date.today(), + 'start_date': datetime.now(tz=timezone.utc), 'created_by': 'Alembic' } ] diff --git a/pay-api/migrations/versions/6f0fe9f23d8c_distribution_code_changes.py b/pay-api/migrations/versions/6f0fe9f23d8c_distribution_code_changes.py index 4765ea3ed..939725b78 100644 --- a/pay-api/migrations/versions/6f0fe9f23d8c_distribution_code_changes.py +++ b/pay-api/migrations/versions/6f0fe9f23d8c_distribution_code_changes.py @@ -5,14 +5,14 @@ Create Date: 2021-01-27 14:07:28.400759 """ -from datetime import datetime +from datetime import datetime, timezone import sqlalchemy as sa from alembic import op from sqlalchemy.dialects import postgresql -today = datetime.today().strftime('%Y-%m-%d') +today = datetime.now(tz=timezone.utc).strftime('%Y-%m-%d') # revision identifiers, used by Alembic. revision = '6f0fe9f23d8c' @@ -98,7 +98,7 @@ def upgrade(): if service_fee_distribution_code_id is None: op.execute( "insert into distribution_codes (created_on, name, client, responsibility_centre, service_line, stob, project_code, start_date, created_by)" - f" values ('{datetime.now()}', '{service_fee_memo_name}', '{service_fee_client}', '{service_fee_responsibility_centre}', '{service_fee_line}'," + f" values ('{datetime.now(tz=timezone.utc)}', '{service_fee_memo_name}', '{service_fee_client}', '{service_fee_responsibility_centre}', '{service_fee_line}'," f"'{service_fee_stob}', '{service_fee_project_code}', '{today}', 'alembic');") # Get the inserted record id diff --git a/pay-api/migrations/versions/7ea7ba8fe991_alteration_filing.py b/pay-api/migrations/versions/7ea7ba8fe991_alteration_filing.py index 0e0b0fe03..f93ebf67c 100644 --- a/pay-api/migrations/versions/7ea7ba8fe991_alteration_filing.py +++ b/pay-api/migrations/versions/7ea7ba8fe991_alteration_filing.py @@ -5,7 +5,7 @@ Create Date: 2020-07-27 16:45:41.623672 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, String @@ -56,7 +56,7 @@ def upgrade(): "filing_type_code": "ALTER", "corp_type_code": "BC", "fee_code": "EN105", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": "FUT01", "priority_fee_code": "PRI01", diff --git a/pay-api/migrations/versions/9c8a93ba9da2_restricted_ppr_fee_codes.py b/pay-api/migrations/versions/9c8a93ba9da2_restricted_ppr_fee_codes.py index 6f17c48c0..3e36e5e45 100644 --- a/pay-api/migrations/versions/9c8a93ba9da2_restricted_ppr_fee_codes.py +++ b/pay-api/migrations/versions/9c8a93ba9da2_restricted_ppr_fee_codes.py @@ -5,7 +5,7 @@ Create Date: 2021-06-04 08:27:25.551857 """ -from datetime import date +from datetime import datetime, timezone import sqlalchemy as sa from alembic import op @@ -58,7 +58,7 @@ def upgrade(): op.bulk_insert( fee_schedule_table, [ - {'filing_type_code': 'MAREG', 'corp_type_code': 'RPPR', 'fee_code': 'EN107', 'fee_start_date': date.today(), + {'filing_type_code': 'MAREG', 'corp_type_code': 'RPPR', 'fee_code': 'EN107', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, ] ) diff --git a/pay-api/migrations/versions/a11be9fe1a6a_distribution_code.py b/pay-api/migrations/versions/a11be9fe1a6a_distribution_code.py index 4ce3791d9..ecf7da06d 100644 --- a/pay-api/migrations/versions/a11be9fe1a6a_distribution_code.py +++ b/pay-api/migrations/versions/a11be9fe1a6a_distribution_code.py @@ -5,7 +5,7 @@ Create Date: 2020-07-27 10:33:34.602674 """ -from datetime import date +from datetime import datetime, timezone import sqlalchemy as sa from alembic import op @@ -72,42 +72,42 @@ def upgrade(): { 'distribution_code_id': 1, 'created_by': 'Alembic', - 'created_on': date.today(), + 'created_on': datetime.now(tz=timezone.utc), 'memo_name': 'CO-OP Filing', 'service_fee_memo_name': None, - 'start_date': date.today(), + 'start_date': datetime.now(tz=timezone.utc), }, { 'distribution_code_id': 2, 'created_by': 'Alembic', - 'created_on': date.today(), + 'created_on': datetime.now(tz=timezone.utc), 'memo_name': 'Benefit Companies', 'service_fee_memo_name': 'SBC Modernization Service Charge', - 'start_date': date.today(), + 'start_date': datetime.now(tz=timezone.utc), }, { 'distribution_code_id': 3, 'created_by': 'Alembic', - 'created_on': date.today(), + 'created_on': datetime.now(tz=timezone.utc), 'memo_name': 'Benefit Companies', 'service_fee_memo_name': 'SBC Modernization Service Charge', - 'start_date': date.today(), + 'start_date': datetime.now(tz=timezone.utc), }, { 'distribution_code_id': 4, 'created_by': 'Alembic', - 'created_on': date.today(), + 'created_on': datetime.now(tz=timezone.utc), 'memo_name': 'VS', 'service_fee_memo_name': 'SBC Modernization Service Charge', - 'start_date': date.today(), + 'start_date': datetime.now(tz=timezone.utc), }, { 'distribution_code_id': 5, 'created_by': 'Alembic', - 'created_on': date.today(), + 'created_on': datetime.now(tz=timezone.utc), 'memo_name': 'PPR', 'service_fee_memo_name': 'SBC Modernization Service Charge', - 'start_date': date.today(), + 'start_date': datetime.now(tz=timezone.utc), } ] diff --git a/pay-api/migrations/versions/b336780735dc_registration_filing_type.py b/pay-api/migrations/versions/b336780735dc_registration_filing_type.py index 43adb8c88..05e3b1f32 100644 --- a/pay-api/migrations/versions/b336780735dc_registration_filing_type.py +++ b/pay-api/migrations/versions/b336780735dc_registration_filing_type.py @@ -5,7 +5,7 @@ Create Date: 2021-11-30 09:33:57.641970 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, String, Boolean, Float, text @@ -95,7 +95,7 @@ def upgrade(): 'filing_type_code': 'FRREG', 'corp_type_code': 'SP', 'fee_code': 'EN116', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'priority_fee_code': 'PRI01' @@ -104,7 +104,7 @@ def upgrade(): 'filing_type_code': 'FRREG', 'corp_type_code': 'GP', 'fee_code': 'EN116', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', 'priority_fee_code': 'PRI01' diff --git a/pay-api/migrations/versions/b7443d501d98_entity_fee_codes.py b/pay-api/migrations/versions/b7443d501d98_entity_fee_codes.py index 44d37f814..aa9c20740 100644 --- a/pay-api/migrations/versions/b7443d501d98_entity_fee_codes.py +++ b/pay-api/migrations/versions/b7443d501d98_entity_fee_codes.py @@ -5,7 +5,7 @@ Create Date: 2020-11-18 16:23:31.943979 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, Float, String, text @@ -92,7 +92,7 @@ def upgrade(): "filing_type_code": "TRANS", "corp_type_code": "BC", "fee_code": "EN107", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -102,7 +102,7 @@ def upgrade(): "filing_type_code": "TRANS", "corp_type_code": "BEN", "fee_code": "EN107", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, @@ -112,7 +112,7 @@ def upgrade(): "filing_type_code": "ALTER", "corp_type_code": "ULC", "fee_code": "EN113", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": 'FUT01', "priority_fee_code": 'PRI01', diff --git a/pay-api/migrations/versions/bae02665e807_adding_zero_dollar_data.py b/pay-api/migrations/versions/bae02665e807_adding_zero_dollar_data.py index fab56ebff..a5f9f7a41 100644 --- a/pay-api/migrations/versions/bae02665e807_adding_zero_dollar_data.py +++ b/pay-api/migrations/versions/bae02665e807_adding_zero_dollar_data.py @@ -5,7 +5,7 @@ Create Date: 2019-09-27 10:56:24.379903 """ -from datetime import date +from datetime import datetime, timezone import sqlalchemy as sa from alembic import op @@ -61,7 +61,7 @@ def upgrade(): op.bulk_insert( fee_schedule_table, [ - {'filing_type_code': 'OTFDR', 'corp_type_code': 'CP', 'fee_code': 'EN107', 'fee_start_date': date.today(), + {'filing_type_code': 'OTFDR', 'corp_type_code': 'CP', 'fee_code': 'EN107', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None} ] ) diff --git a/pay-api/migrations/versions/c67213f860ea_incorporation_fee_codes.py b/pay-api/migrations/versions/c67213f860ea_incorporation_fee_codes.py index 6de5707af..37e14ea99 100644 --- a/pay-api/migrations/versions/c67213f860ea_incorporation_fee_codes.py +++ b/pay-api/migrations/versions/c67213f860ea_incorporation_fee_codes.py @@ -5,7 +5,7 @@ Create Date: 2021-06-10 14:20:33.590691 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, String, Boolean, text, Integer @@ -72,7 +72,7 @@ def upgrade(): "filing_type_code": "BCINC", "corp_type_code": "LTD", "fee_code": "EN109", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": "FUT01", "priority_fee_code": "PRI01", @@ -82,7 +82,7 @@ def upgrade(): "filing_type_code": "BCINC", "corp_type_code": "BC", "fee_code": "EN109", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": "FUT01", "priority_fee_code": "PRI01", @@ -92,7 +92,7 @@ def upgrade(): "filing_type_code": "BCINC", "corp_type_code": "CCC", "fee_code": "EN109", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": "FUT01", "priority_fee_code": "PRI01", @@ -102,7 +102,7 @@ def upgrade(): "filing_type_code": "BCINC", "corp_type_code": "ULC", "fee_code": "EN113", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": "FUT01", "priority_fee_code": "PRI01", diff --git a/pay-api/migrations/versions/c871202927f0_rush_fee_code.py b/pay-api/migrations/versions/c871202927f0_rush_fee_code.py index 98519de3a..e9c6b8ffb 100644 --- a/pay-api/migrations/versions/c871202927f0_rush_fee_code.py +++ b/pay-api/migrations/versions/c871202927f0_rush_fee_code.py @@ -5,7 +5,7 @@ Create Date: 2021-07-13 14:34:48.482182 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, String, Float, text @@ -63,7 +63,7 @@ def upgrade(): "filing_type_code": "WILLRUSH", "corp_type_code": "VS", "fee_code": "EN204", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None, diff --git a/pay-api/migrations/versions/daa392b64cb7_fee_master_inserts.py b/pay-api/migrations/versions/daa392b64cb7_fee_master_inserts.py index 64d10a387..b808570dd 100644 --- a/pay-api/migrations/versions/daa392b64cb7_fee_master_inserts.py +++ b/pay-api/migrations/versions/daa392b64cb7_fee_master_inserts.py @@ -5,7 +5,7 @@ Create Date: 2019-05-10 11:07:41.003718 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, Float, Integer, String @@ -77,27 +77,27 @@ def upgrade(): op.bulk_insert( fee_schedule_table, [ - {'filing_type_code': 'OTANN', 'corp_type_code': 'CP', 'fee_code': 'EN103', 'fee_start_date': date.today(), + {'filing_type_code': 'OTANN', 'corp_type_code': 'CP', 'fee_code': 'EN103', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'OTADD', 'corp_type_code': 'CP', 'fee_code': 'EN101', 'fee_start_date': date.today(), + {'filing_type_code': 'OTADD', 'corp_type_code': 'CP', 'fee_code': 'EN101', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'OTCDR', 'corp_type_code': 'CP', 'fee_code': 'EN101', 'fee_start_date': date.today(), + {'filing_type_code': 'OTCDR', 'corp_type_code': 'CP', 'fee_code': 'EN101', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'OTSPE', 'corp_type_code': 'CP', 'fee_code': 'EN104', 'fee_start_date': date.today(), + {'filing_type_code': 'OTSPE', 'corp_type_code': 'CP', 'fee_code': 'EN104', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'OTINC', 'corp_type_code': 'CP', 'fee_code': 'EN106', 'fee_start_date': date.today(), + {'filing_type_code': 'OTINC', 'corp_type_code': 'CP', 'fee_code': 'EN106', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'OTAMA', 'corp_type_code': 'CP', 'fee_code': 'EN106', 'fee_start_date': date.today(), + {'filing_type_code': 'OTAMA', 'corp_type_code': 'CP', 'fee_code': 'EN106', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'OTREG', 'corp_type_code': 'CP', 'fee_code': 'EN106', 'fee_start_date': date.today(), + {'filing_type_code': 'OTREG', 'corp_type_code': 'CP', 'fee_code': 'EN106', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'OTRES', 'corp_type_code': 'CP', 'fee_code': 'EN106', 'fee_start_date': date.today(), + {'filing_type_code': 'OTRES', 'corp_type_code': 'CP', 'fee_code': 'EN106', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'OTAMR', 'corp_type_code': 'CP', 'fee_code': 'EN101', 'fee_start_date': date.today(), + {'filing_type_code': 'OTAMR', 'corp_type_code': 'CP', 'fee_code': 'EN101', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'OTADR', 'corp_type_code': 'CP', 'fee_code': 'EN101', 'fee_start_date': date.today(), + {'filing_type_code': 'OTADR', 'corp_type_code': 'CP', 'fee_code': 'EN101', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None}, - {'filing_type_code': 'OTCGM', 'corp_type_code': 'CP', 'fee_code': 'EN107', 'fee_start_date': date.today(), + {'filing_type_code': 'OTCGM', 'corp_type_code': 'CP', 'fee_code': 'EN107', 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None} ] ) diff --git a/pay-api/migrations/versions/dbe9dc38ac33_firms_fee_codes.py b/pay-api/migrations/versions/dbe9dc38ac33_firms_fee_codes.py index 7e1fc5a6f..9507c41c4 100644 --- a/pay-api/migrations/versions/dbe9dc38ac33_firms_fee_codes.py +++ b/pay-api/migrations/versions/dbe9dc38ac33_firms_fee_codes.py @@ -5,7 +5,7 @@ Create Date: 2022-03-30 15:04:45.566151 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, String, text @@ -43,7 +43,7 @@ def upgrade(): 'filing_type_code': 'DIS_VOL', 'corp_type_code': 'SP', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, @@ -53,7 +53,7 @@ def upgrade(): 'filing_type_code': 'DIS_VOL', 'corp_type_code': 'GP', 'fee_code': 'EN107', - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': None, 'future_effective_fee_code': None, diff --git a/pay-api/migrations/versions/e699674b1774_nsf_fee_code.py b/pay-api/migrations/versions/e699674b1774_nsf_fee_code.py index 04fa70bb9..0a657cefc 100644 --- a/pay-api/migrations/versions/e699674b1774_nsf_fee_code.py +++ b/pay-api/migrations/versions/e699674b1774_nsf_fee_code.py @@ -5,7 +5,7 @@ Create Date: 2020-11-27 10:28:35.417779 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, Float, String @@ -75,7 +75,7 @@ def upgrade(): "filing_type_code": "NSF", "corp_type_code": "BCR", "fee_code": "NSF01", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None } ] diff --git a/pay-api/migrations/versions/e6eb14b9d50e_fee_master_for_bcorps.py b/pay-api/migrations/versions/e6eb14b9d50e_fee_master_for_bcorps.py index 72f2845db..c8b099ba4 100644 --- a/pay-api/migrations/versions/e6eb14b9d50e_fee_master_for_bcorps.py +++ b/pay-api/migrations/versions/e6eb14b9d50e_fee_master_for_bcorps.py @@ -5,7 +5,7 @@ Create Date: 2019-10-29 10:44:55.220375 """ -from datetime import date +from datetime import datetime, timezone import sqlalchemy as sa from alembic import op @@ -48,84 +48,84 @@ def upgrade(): "filing_type_code": "OTANN", "corp_type_code": "BC", "fee_code": "EN108", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, }, { "filing_type_code": "OTADD", "corp_type_code": "BC", "fee_code": "EN101", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, }, { "filing_type_code": "OTCDR", "corp_type_code": "BC", "fee_code": "EN101", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, }, { "filing_type_code": "OTSPE", "corp_type_code": "BC", "fee_code": "EN104", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, }, { "filing_type_code": "OTINC", "corp_type_code": "BC", "fee_code": "EN106", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, }, { "filing_type_code": "OTAMA", "corp_type_code": "BC", "fee_code": "EN106", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, }, { "filing_type_code": "OTREG", "corp_type_code": "BC", "fee_code": "EN106", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, }, { "filing_type_code": "OTRES", "corp_type_code": "BC", "fee_code": "EN106", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, }, { "filing_type_code": "OTAMR", "corp_type_code": "BC", "fee_code": "EN101", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, }, { "filing_type_code": "OTADR", "corp_type_code": "BC", "fee_code": "EN101", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, }, { "filing_type_code": "OTCGM", "corp_type_code": "BC", "fee_code": "EN107", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, }, { "filing_type_code": "OTFDR", "corp_type_code": "BC", "fee_code": "EN107", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, }, ], diff --git a/pay-api/migrations/versions/e748b5c19247_.py b/pay-api/migrations/versions/e748b5c19247_.py index 7f60b7356..3afadf43c 100644 --- a/pay-api/migrations/versions/e748b5c19247_.py +++ b/pay-api/migrations/versions/e748b5c19247_.py @@ -5,7 +5,7 @@ Create Date: 2022-05-12 12:30:13.857906 """ -from datetime import date +from datetime import datetime, timezone from re import M from alembic import op @@ -86,7 +86,7 @@ def upgrade(): 'filing_type_code': 'OLAARTOQ', 'corp_type_code': 'BCA', 'fee_code': 'EN107', # 0.00 - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', #$1.50 default 'variable': True @@ -95,7 +95,7 @@ def upgrade(): 'filing_type_code': 'OLAARTAQ', 'corp_type_code': 'BCA', 'fee_code': 'EN107', # 0.00 - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', #$1.50 default 'variable': True @@ -104,7 +104,7 @@ def upgrade(): 'filing_type_code': 'OLAARTIQ', 'corp_type_code': 'BCA', 'fee_code': 'EN107', # 0.00 - 'fee_start_date': date.today(), + 'fee_start_date': datetime.now(tz=timezone.utc), 'fee_end_date': None, 'service_fee_code': 'TRF01', #$1.50 default 'variable': True @@ -115,14 +115,14 @@ def upgrade(): op.bulk_insert(distribution_code_table, [ { - 'created_on': date.today(), + 'created_on': datetime.now(tz=timezone.utc), 'name': 'BC Assessment', 'client': '112', 'responsibility_centre': '32041', 'service_line': '35301', 'stob': '1278', 'project_code': '3200000', - 'start_date': date.today(), + 'start_date': datetime.now(tz=timezone.utc), 'created_by': 'Alembic' } ] diff --git a/pay-api/migrations/versions/e8edc889072d_nr_fee_codes.py b/pay-api/migrations/versions/e8edc889072d_nr_fee_codes.py index a50e75d71..63f3fb325 100644 --- a/pay-api/migrations/versions/e8edc889072d_nr_fee_codes.py +++ b/pay-api/migrations/versions/e8edc889072d_nr_fee_codes.py @@ -5,7 +5,7 @@ Create Date: 2020-06-23 11:06:16.156584 """ -from datetime import date +from datetime import datetime, timezone from alembic import op from sqlalchemy import Date, Float, String @@ -74,7 +74,7 @@ def upgrade(): "filing_type_code": "NM620", "corp_type_code": "NRO", "fee_code": "EN103", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": "PRI01" @@ -83,7 +83,7 @@ def upgrade(): "filing_type_code": "NM606", "corp_type_code": "NRO", "fee_code": "EN105", - "fee_start_date": date.today(), + "fee_start_date": datetime.now(tz=timezone.utc), "fee_end_date": None, "future_effective_fee_code": None, "priority_fee_code": None diff --git a/pay-api/poetry.lock b/pay-api/poetry.lock index f173b8c54..0afdd931d 100644 --- a/pay-api/poetry.lock +++ b/pay-api/poetry.lock @@ -1,91 +1,103 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] -name = "aiohttp" -version = "3.9.5" -description = "Async http client/server framework (asyncio)" +name = "aiohappyeyeballs" +version = "2.3.6" +description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, - {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, - {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, - {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, - {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, - {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, - {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, - {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, - {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, - {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, + {file = "aiohappyeyeballs-2.3.6-py3-none-any.whl", hash = "sha256:15dca2611fa78442f1cb54cf07ffb998573f2b4fbeab45ca8554c045665c896b"}, + {file = "aiohappyeyeballs-2.3.6.tar.gz", hash = "sha256:88211068d2a40e0436033956d7de3926ff36d54776f8b1022d6b21320cadae79"}, ] -[package.dependencies] +[[package]] +name = "aiohttp" +version = "3.10.3" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc36cbdedf6f259371dbbbcaae5bb0e95b879bc501668ab6306af867577eb5db"}, + {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85466b5a695c2a7db13eb2c200af552d13e6a9313d7fa92e4ffe04a2c0ea74c1"}, + {file = "aiohttp-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:71bb1d97bfe7e6726267cea169fdf5df7658831bb68ec02c9c6b9f3511e108bb"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baec1eb274f78b2de54471fc4c69ecbea4275965eab4b556ef7a7698dee18bf2"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13031e7ec1188274bad243255c328cc3019e36a5a907978501256000d57a7201"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bbc55a964b8eecb341e492ae91c3bd0848324d313e1e71a27e3d96e6ee7e8e8"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8cc0564b286b625e673a2615ede60a1704d0cbbf1b24604e28c31ed37dc62aa"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f817a54059a4cfbc385a7f51696359c642088710e731e8df80d0607193ed2b73"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8542c9e5bcb2bd3115acdf5adc41cda394e7360916197805e7e32b93d821ef93"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:671efce3a4a0281060edf9a07a2f7e6230dca3a1cbc61d110eee7753d28405f7"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0974f3b5b0132edcec92c3306f858ad4356a63d26b18021d859c9927616ebf27"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:44bb159b55926b57812dca1b21c34528e800963ffe130d08b049b2d6b994ada7"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6ae9ae382d1c9617a91647575255ad55a48bfdde34cc2185dd558ce476bf16e9"}, + {file = "aiohttp-3.10.3-cp310-cp310-win32.whl", hash = "sha256:aed12a54d4e1ee647376fa541e1b7621505001f9f939debf51397b9329fd88b9"}, + {file = "aiohttp-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:b51aef59370baf7444de1572f7830f59ddbabd04e5292fa4218d02f085f8d299"}, + {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e021c4c778644e8cdc09487d65564265e6b149896a17d7c0f52e9a088cc44e1b"}, + {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:24fade6dae446b183e2410a8628b80df9b7a42205c6bfc2eff783cbeedc224a2"}, + {file = "aiohttp-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bc8e9f15939dacb0e1f2d15f9c41b786051c10472c7a926f5771e99b49a5957f"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5a9ec959b5381271c8ec9310aae1713b2aec29efa32e232e5ef7dcca0df0279"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a5d0ea8a6467b15d53b00c4e8ea8811e47c3cc1bdbc62b1aceb3076403d551f"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9ed607dbbdd0d4d39b597e5bf6b0d40d844dfb0ac6a123ed79042ef08c1f87e"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e66d5b506832e56add66af88c288c1d5ba0c38b535a1a59e436b300b57b23e"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fda91ad797e4914cca0afa8b6cccd5d2b3569ccc88731be202f6adce39503189"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:61ccb867b2f2f53df6598eb2a93329b5eee0b00646ee79ea67d68844747a418e"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d881353264e6156f215b3cb778c9ac3184f5465c2ece5e6fce82e68946868ef"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b031ce229114825f49cec4434fa844ccb5225e266c3e146cb4bdd025a6da52f1"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5337cc742a03f9e3213b097abff8781f79de7190bbfaa987bd2b7ceb5bb0bdec"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab3361159fd3dcd0e48bbe804006d5cfb074b382666e6c064112056eb234f1a9"}, + {file = "aiohttp-3.10.3-cp311-cp311-win32.whl", hash = "sha256:05d66203a530209cbe40f102ebaac0b2214aba2a33c075d0bf825987c36f1f0b"}, + {file = "aiohttp-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:70b4a4984a70a2322b70e088d654528129783ac1ebbf7dd76627b3bd22db2f17"}, + {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:166de65e2e4e63357cfa8417cf952a519ac42f1654cb2d43ed76899e2319b1ee"}, + {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7084876352ba3833d5d214e02b32d794e3fd9cf21fdba99cff5acabeb90d9806"}, + {file = "aiohttp-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d98c604c93403288591d7d6d7d6cc8a63459168f8846aeffd5b3a7f3b3e5e09"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d73b073a25a0bb8bf014345374fe2d0f63681ab5da4c22f9d2025ca3e3ea54fc"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8da6b48c20ce78f5721068f383e0e113dde034e868f1b2f5ee7cb1e95f91db57"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a9dcdccf50284b1b0dc72bc57e5bbd3cc9bf019060dfa0668f63241ccc16aa7"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56fb94bae2be58f68d000d046172d8b8e6b1b571eb02ceee5535e9633dcd559c"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf75716377aad2c718cdf66451c5cf02042085d84522aec1f9246d3e4b8641a6"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c51ed03e19c885c8e91f574e4bbe7381793f56f93229731597e4a499ffef2a5"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b84857b66fa6510a163bb083c1199d1ee091a40163cfcbbd0642495fed096204"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c124b9206b1befe0491f48185fd30a0dd51b0f4e0e7e43ac1236066215aff272"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3461d9294941937f07bbbaa6227ba799bc71cc3b22c40222568dc1cca5118f68"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08bd0754d257b2db27d6bab208c74601df6f21bfe4cb2ec7b258ba691aac64b3"}, + {file = "aiohttp-3.10.3-cp312-cp312-win32.whl", hash = "sha256:7f9159ae530297f61a00116771e57516f89a3de6ba33f314402e41560872b50a"}, + {file = "aiohttp-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:e1128c5d3a466279cb23c4aa32a0f6cb0e7d2961e74e9e421f90e74f75ec1edf"}, + {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d1100e68e70eb72eadba2b932b185ebf0f28fd2f0dbfe576cfa9d9894ef49752"}, + {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a541414578ff47c0a9b0b8b77381ea86b0c8531ab37fc587572cb662ccd80b88"}, + {file = "aiohttp-3.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d5548444ef60bf4c7b19ace21f032fa42d822e516a6940d36579f7bfa8513f9c"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba2e838b5e6a8755ac8297275c9460e729dc1522b6454aee1766c6de6d56e5e"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48665433bb59144aaf502c324694bec25867eb6630fcd831f7a893ca473fcde4"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bac352fceed158620ce2d701ad39d4c1c76d114255a7c530e057e2b9f55bdf9f"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0f670502100cdc567188c49415bebba947eb3edaa2028e1a50dd81bd13363f"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b09f38a67679e32d380fe512189ccb0b25e15afc79b23fbd5b5e48e4fc8fd9"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:cd788602e239ace64f257d1c9d39898ca65525583f0fbf0988bcba19418fe93f"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:214277dcb07ab3875f17ee1c777d446dcce75bea85846849cc9d139ab8f5081f"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:32007fdcaab789689c2ecaaf4b71f8e37bf012a15cd02c0a9db8c4d0e7989fa8"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:123e5819bfe1b87204575515cf448ab3bf1489cdeb3b61012bde716cda5853e7"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:812121a201f0c02491a5db335a737b4113151926a79ae9ed1a9f41ea225c0e3f"}, + {file = "aiohttp-3.10.3-cp38-cp38-win32.whl", hash = "sha256:b97dc9a17a59f350c0caa453a3cb35671a2ffa3a29a6ef3568b523b9113d84e5"}, + {file = "aiohttp-3.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:3731a73ddc26969d65f90471c635abd4e1546a25299b687e654ea6d2fc052394"}, + {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38d91b98b4320ffe66efa56cb0f614a05af53b675ce1b8607cdb2ac826a8d58e"}, + {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9743fa34a10a36ddd448bba8a3adc2a66a1c575c3c2940301bacd6cc896c6bf1"}, + {file = "aiohttp-3.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7c126f532caf238031c19d169cfae3c6a59129452c990a6e84d6e7b198a001dc"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:926e68438f05703e500b06fe7148ef3013dd6f276de65c68558fa9974eeb59ad"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:434b3ab75833accd0b931d11874e206e816f6e6626fd69f643d6a8269cd9166a"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d35235a44ec38109b811c3600d15d8383297a8fab8e3dec6147477ec8636712a"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59c489661edbd863edb30a8bd69ecb044bd381d1818022bc698ba1b6f80e5dd1"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50544fe498c81cb98912afabfc4e4d9d85e89f86238348e3712f7ca6a2f01dab"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:09bc79275737d4dc066e0ae2951866bb36d9c6b460cb7564f111cc0427f14844"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:af4dbec58e37f5afff4f91cdf235e8e4b0bd0127a2a4fd1040e2cad3369d2f06"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b22cae3c9dd55a6b4c48c63081d31c00fc11fa9db1a20c8a50ee38c1a29539d2"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ba562736d3fbfe9241dad46c1a8994478d4a0e50796d80e29d50cabe8fbfcc3f"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f25d6c4e82d7489be84f2b1c8212fafc021b3731abdb61a563c90e37cced3a21"}, + {file = "aiohttp-3.10.3-cp39-cp39-win32.whl", hash = "sha256:b69d832e5f5fa15b1b6b2c8eb6a9fd2c0ec1fd7729cb4322ed27771afc9fc2ac"}, + {file = "aiohttp-3.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:673bb6e3249dc8825df1105f6ef74e2eab779b7ff78e96c15cadb78b04a83752"}, + {file = "aiohttp-3.10.3.tar.gz", hash = "sha256:21650e7032cc2d31fc23d353d7123e771354f2a3d5b05a5647fc30fea214e696"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" aiosignal = ">=1.1.2" attrs = ">=17.3.0" frozenlist = ">=1.1.1" @@ -93,7 +105,7 @@ multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "brotlicffi"] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] [[package]] name = "aiosignal" @@ -141,13 +153,13 @@ files = [ [[package]] name = "astroid" -version = "3.2.2" +version = "3.2.4" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.8.0" files = [ - {file = "astroid-3.2.2-py3-none-any.whl", hash = "sha256:e8a0083b4bb28fcffb6207a3bfc9e5d0a68be951dd7e336d5dcf639c682388c0"}, - {file = "astroid-3.2.2.tar.gz", hash = "sha256:8ead48e31b92b2e217b6c9733a21afafe479d52d6e164dd25fb1a770c7c3cf94"}, + {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"}, + {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, ] [[package]] @@ -171,17 +183,17 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "autopep8" -version = "2.2.0" +version = "2.3.1" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" optional = false python-versions = ">=3.8" files = [ - {file = "autopep8-2.2.0-py2.py3-none-any.whl", hash = "sha256:05418a981f038969d8bdcd5636bf15948db7555ae944b9f79b5a34b35f1370d4"}, - {file = "autopep8-2.2.0.tar.gz", hash = "sha256:d306a0581163ac29908280ad557773a95a9bede072c0fafed6f141f5311f43c1"}, + {file = "autopep8-2.3.1-py2.py3-none-any.whl", hash = "sha256:a203fe0fcad7939987422140ab17a930f684763bf7335bdb6709991dd7ef6c2d"}, + {file = "autopep8-2.3.1.tar.gz", hash = "sha256:8d6c87eba648fdcfc83e29b788910b8643171c395d9c4bcf115ece035b9c9dda"}, ] [package.dependencies] -pycodestyle = ">=2.11.0" +pycodestyle = ">=2.12.0" [[package]] name = "blinker" @@ -241,13 +253,13 @@ ujson = ["ujson (>=5.7.0)"] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.7.4" 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"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -440,63 +452,83 @@ files = [ [[package]] name = "coverage" -version = "7.5.3" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, - {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, - {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, - {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, - {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, - {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, - {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, - {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, - {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, - {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, - {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, - {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, - {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, - {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.extras] @@ -519,43 +551,38 @@ pytz = ">2021.1" [[package]] name = "cryptography" -version = "42.0.5" +version = "43.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, + {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, + {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, + {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, + {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, + {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, + {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, + {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, ] [package.dependencies] @@ -568,7 +595,7 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] @@ -645,18 +672,18 @@ python-dateutil = ">=2.4" [[package]] name = "flake8" -version = "7.0.0" +version = "7.1.1" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" files = [ - {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, - {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, + {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, + {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.11.0,<2.12.0" +pycodestyle = ">=2.12.0,<2.13.0" pyflakes = ">=3.2.0,<3.3.0" [[package]] @@ -770,13 +797,13 @@ Flask = "*" [[package]] name = "flask-cors" -version = "4.0.0" +version = "5.0.0" description = "A Flask extension adding a decorator for CORS support" optional = false python-versions = "*" files = [ - {file = "Flask-Cors-4.0.0.tar.gz", hash = "sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"}, - {file = "Flask_Cors-4.0.0-py2.py3-none-any.whl", hash = "sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783"}, + {file = "Flask_Cors-5.0.0-py2.py3-none-any.whl", hash = "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc"}, + {file = "flask_cors-5.0.0.tar.gz", hash = "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef"}, ] [package.dependencies] @@ -1019,18 +1046,18 @@ simple-cloudevent = {git = "https://github.com/daxiom/simple-cloudevent.py.git"} type = "git" url = "https://github.com/bcgov/sbc-connect-common.git" reference = "main" -resolved_reference = "1c0ea85fc4a6b8862fb78ace245a30c0a449a7c3" +resolved_reference = "c898988d239dc261b2b186465a1887f15512c102" subdirectory = "python/gcp-queue" [[package]] name = "google-api-core" -version = "2.19.0" +version = "2.19.1" description = "Google API client core library" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-core-2.19.0.tar.gz", hash = "sha256:cf1b7c2694047886d2af1128a03ae99e391108a08804f87cfd35970e49c9cd10"}, - {file = "google_api_core-2.19.0-py3-none-any.whl", hash = "sha256:8661eec4078c35428fd3f69a2c7ee29e342896b70f01d1a1cbcb334372dd6251"}, + {file = "google-api-core-2.19.1.tar.gz", hash = "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd"}, + {file = "google_api_core-2.19.1-py3-none-any.whl", hash = "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125"}, ] [package.dependencies] @@ -1039,7 +1066,7 @@ googleapis-common-protos = ">=1.56.2,<2.0.dev0" grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" requests = ">=2.18.0,<3.0.0.dev0" [package.extras] @@ -1049,13 +1076,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-auth" -version = "2.29.0" +version = "2.33.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google-auth-2.29.0.tar.gz", hash = "sha256:672dff332d073227550ffc7457868ac4218d6c500b155fe6cc17d2b13602c360"}, - {file = "google_auth-2.29.0-py2.py3-none-any.whl", hash = "sha256:d452ad095688cd52bae0ad6fafe027f6a6d6f560e810fec20914e17a09526415"}, + {file = "google_auth-2.33.0-py2.py3-none-any.whl", hash = "sha256:8eff47d0d4a34ab6265c50a106a3362de6a9975bb08998700e389f857e4d39df"}, + {file = "google_auth-2.33.0.tar.gz", hash = "sha256:d6a52342160d7290e334b4d47ba390767e4438ad0d45b7630774533e82655b95"}, ] [package.dependencies] @@ -1072,13 +1099,13 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "google-cloud-pubsub" -version = "2.21.2" +version = "2.23.0" description = "Google Cloud Pub/Sub API client library" optional = false python-versions = ">=3.7" files = [ - {file = "google-cloud-pubsub-2.21.2.tar.gz", hash = "sha256:fc72226b14731db2873f7c4031cc757e274bbcdabcac7523b2cd6e46130d6096"}, - {file = "google_cloud_pubsub-2.21.2-py2.py3-none-any.whl", hash = "sha256:05a6b01e5bda6f4a4858700e3e9a12e3080589718d648b2383e5818131db9ce4"}, + {file = "google_cloud_pubsub-2.23.0-py2.py3-none-any.whl", hash = "sha256:d341b2df8b105d0e3774b4bc9173bc0cf26bced31a4736cd9eefa33453b75dff"}, + {file = "google_cloud_pubsub-2.23.0.tar.gz", hash = "sha256:cf3d6f2ab11b5c8dfc0aa7d4cae5ee1d66b408d9666f1762c9c17e269cd8b658"}, ] [package.dependencies] @@ -1088,25 +1115,25 @@ grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" grpcio = ">=1.51.3,<2.0dev" grpcio-status = ">=1.33.2" proto-plus = {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" +protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" [package.extras] libcst = ["libcst (>=0.3.10)"] [[package]] name = "googleapis-common-protos" -version = "1.63.0" +version = "1.63.2" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.63.0.tar.gz", hash = "sha256:17ad01b11d5f1d0171c06d3ba5c04c54474e883b66b949722b4938ee2694ef4e"}, - {file = "googleapis_common_protos-1.63.0-py2.py3-none-any.whl", hash = "sha256:ae45f75702f7c08b541f750854a678bd8f534a1a6bace6afe975f1d0a82d6632"}, + {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, + {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, ] [package.dependencies] grpcio = {version = ">=1.44.0,<2.0.0.dev0", optional = true, markers = "extra == \"grpc\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] @@ -1184,112 +1211,113 @@ test = ["objgraph", "psutil"] [[package]] name = "grpc-google-iam-v1" -version = "0.13.0" +version = "0.13.1" description = "IAM API client library" optional = false python-versions = ">=3.7" files = [ - {file = "grpc-google-iam-v1-0.13.0.tar.gz", hash = "sha256:fad318608b9e093258fbf12529180f400d1c44453698a33509cc6ecf005b294e"}, - {file = "grpc_google_iam_v1-0.13.0-py2.py3-none-any.whl", hash = "sha256:53902e2af7de8df8c1bd91373d9be55b0743ec267a7428ea638db3775becae89"}, + {file = "grpc-google-iam-v1-0.13.1.tar.gz", hash = "sha256:3ff4b2fd9d990965e410965253c0da6f66205d5a8291c4c31c6ebecca18a9001"}, + {file = "grpc_google_iam_v1-0.13.1-py2.py3-none-any.whl", hash = "sha256:c3e86151a981811f30d5e7330f271cee53e73bb87755e88cc3b6f0c7b5fe374e"}, ] [package.dependencies] googleapis-common-protos = {version = ">=1.56.0,<2.0.0dev", extras = ["grpc"]} grpcio = ">=1.44.0,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" [[package]] name = "grpcio" -version = "1.64.0" +version = "1.65.4" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.64.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:3b09c3d9de95461214a11d82cc0e6a46a6f4e1f91834b50782f932895215e5db"}, - {file = "grpcio-1.64.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:7e013428ab472892830287dd082b7d129f4d8afef49227a28223a77337555eaa"}, - {file = "grpcio-1.64.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:02cc9cc3f816d30f7993d0d408043b4a7d6a02346d251694d8ab1f78cc723e7e"}, - {file = "grpcio-1.64.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f5de082d936e0208ce8db9095821361dfa97af8767a6607ae71425ac8ace15c"}, - {file = "grpcio-1.64.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b7bf346391dffa182fba42506adf3a84f4a718a05e445b37824136047686a1"}, - {file = "grpcio-1.64.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b2cbdfba18408389a1371f8c2af1659119e1831e5ed24c240cae9e27b4abc38d"}, - {file = "grpcio-1.64.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aca4f15427d2df592e0c8f3d38847e25135e4092d7f70f02452c0e90d6a02d6d"}, - {file = "grpcio-1.64.0-cp310-cp310-win32.whl", hash = "sha256:7c1f5b2298244472bcda49b599be04579f26425af0fd80d3f2eb5fd8bc84d106"}, - {file = "grpcio-1.64.0-cp310-cp310-win_amd64.whl", hash = "sha256:73f84f9e5985a532e47880b3924867de16fa1aa513fff9b26106220c253c70c5"}, - {file = "grpcio-1.64.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2a18090371d138a57714ee9bffd6c9c9cb2e02ce42c681aac093ae1e7189ed21"}, - {file = "grpcio-1.64.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59c68df3a934a586c3473d15956d23a618b8f05b5e7a3a904d40300e9c69cbf0"}, - {file = "grpcio-1.64.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:b52e1ec7185512103dd47d41cf34ea78e7a7361ba460187ddd2416b480e0938c"}, - {file = "grpcio-1.64.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d598b5d5e2c9115d7fb7e2cb5508d14286af506a75950762aa1372d60e41851"}, - {file = "grpcio-1.64.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01615bbcae6875eee8091e6b9414072f4e4b00d8b7e141f89635bdae7cf784e5"}, - {file = "grpcio-1.64.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0b2dfe6dcace264807d9123d483d4c43274e3f8c39f90ff51de538245d7a4145"}, - {file = "grpcio-1.64.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7f17572dc9acd5e6dfd3014d10c0b533e9f79cd9517fc10b0225746f4c24b58e"}, - {file = "grpcio-1.64.0-cp311-cp311-win32.whl", hash = "sha256:6ec5ed15b4ffe56e2c6bc76af45e6b591c9be0224b3fb090adfb205c9012367d"}, - {file = "grpcio-1.64.0-cp311-cp311-win_amd64.whl", hash = "sha256:597191370951b477b7a1441e1aaa5cacebeb46a3b0bd240ec3bb2f28298c7553"}, - {file = "grpcio-1.64.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:1ce4cd5a61d4532651079e7aae0fedf9a80e613eed895d5b9743e66b52d15812"}, - {file = "grpcio-1.64.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:650a8150a9b288f40d5b7c1d5400cc11724eae50bd1f501a66e1ea949173649b"}, - {file = "grpcio-1.64.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:8de0399b983f8676a7ccfdd45e5b2caec74a7e3cc576c6b1eecf3b3680deda5e"}, - {file = "grpcio-1.64.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46b8b43ba6a2a8f3103f103f97996cad507bcfd72359af6516363c48793d5a7b"}, - {file = "grpcio-1.64.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a54362f03d4dcfae63be455d0a7d4c1403673498b92c6bfe22157d935b57c7a9"}, - {file = "grpcio-1.64.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1f8ea18b928e539046bb5f9c124d717fbf00cc4b2d960ae0b8468562846f5aa1"}, - {file = "grpcio-1.64.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c56c91bd2923ddb6e7ed28ebb66d15633b03e0df22206f22dfcdde08047e0a48"}, - {file = "grpcio-1.64.0-cp312-cp312-win32.whl", hash = "sha256:874c741c8a66f0834f653a69e7e64b4e67fcd4a8d40296919b93bab2ccc780ba"}, - {file = "grpcio-1.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:0da1d921f8e4bcee307aeef6c7095eb26e617c471f8cb1c454fd389c5c296d1e"}, - {file = "grpcio-1.64.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:c46fb6bfca17bfc49f011eb53416e61472fa96caa0979b4329176bdd38cbbf2a"}, - {file = "grpcio-1.64.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3d2004e85cf5213995d09408501f82c8534700d2babeb81dfdba2a3bff0bb396"}, - {file = "grpcio-1.64.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:6d5541eb460d73a07418524fb64dcfe0adfbcd32e2dac0f8f90ce5b9dd6c046c"}, - {file = "grpcio-1.64.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f279ad72dd7d64412e10f2443f9f34872a938c67387863c4cd2fb837f53e7d2"}, - {file = "grpcio-1.64.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85fda90b81da25993aa47fae66cae747b921f8f6777550895fb62375b776a231"}, - {file = "grpcio-1.64.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a053584079b793a54bece4a7d1d1b5c0645bdbee729215cd433703dc2532f72b"}, - {file = "grpcio-1.64.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:579dd9fb11bc73f0de061cab5f8b2def21480fd99eb3743ed041ad6a1913ee2f"}, - {file = "grpcio-1.64.0-cp38-cp38-win32.whl", hash = "sha256:23b6887bb21d77649d022fa1859e05853fdc2e60682fd86c3db652a555a282e0"}, - {file = "grpcio-1.64.0-cp38-cp38-win_amd64.whl", hash = "sha256:753cb58683ba0c545306f4e17dabf468d29cb6f6b11832e1e432160bb3f8403c"}, - {file = "grpcio-1.64.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:2186d76a7e383e1466e0ea2b0febc343ffeae13928c63c6ec6826533c2d69590"}, - {file = "grpcio-1.64.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0f30596cdcbed3c98024fb4f1d91745146385b3f9fd10c9f2270cbfe2ed7ed91"}, - {file = "grpcio-1.64.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:d9171f025a196f5bcfec7e8e7ffb7c3535f7d60aecd3503f9e250296c7cfc150"}, - {file = "grpcio-1.64.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf4c8daed18ae2be2f1fc7d613a76ee2a2e28fdf2412d5c128be23144d28283d"}, - {file = "grpcio-1.64.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3550493ac1d23198d46dc9c9b24b411cef613798dc31160c7138568ec26bc9b4"}, - {file = "grpcio-1.64.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3161a8f8bb38077a6470508c1a7301cd54301c53b8a34bb83e3c9764874ecabd"}, - {file = "grpcio-1.64.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e8fabe2cc57a369638ab1ad8e6043721014fdf9a13baa7c0e35995d3a4a7618"}, - {file = "grpcio-1.64.0-cp39-cp39-win32.whl", hash = "sha256:31890b24d47b62cc27da49a462efe3d02f3c120edb0e6c46dcc0025506acf004"}, - {file = "grpcio-1.64.0-cp39-cp39-win_amd64.whl", hash = "sha256:5a56797dea8c02e7d3a85dfea879f286175cf4d14fbd9ab3ef2477277b927baa"}, - {file = "grpcio-1.64.0.tar.gz", hash = "sha256:257baf07f53a571c215eebe9679c3058a313fd1d1f7c4eede5a8660108c52d9c"}, + {file = "grpcio-1.65.4-cp310-cp310-linux_armv7l.whl", hash = "sha256:0e85c8766cf7f004ab01aff6a0393935a30d84388fa3c58d77849fcf27f3e98c"}, + {file = "grpcio-1.65.4-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:e4a795c02405c7dfa8affd98c14d980f4acea16ea3b539e7404c645329460e5a"}, + {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d7b984a8dd975d949c2042b9b5ebcf297d6d5af57dcd47f946849ee15d3c2fb8"}, + {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644a783ce604a7d7c91412bd51cf9418b942cf71896344b6dc8d55713c71ce82"}, + {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5764237d751d3031a36fafd57eb7d36fd2c10c658d2b4057c516ccf114849a3e"}, + {file = "grpcio-1.65.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ee40d058cf20e1dd4cacec9c39e9bce13fedd38ce32f9ba00f639464fcb757de"}, + {file = "grpcio-1.65.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4482a44ce7cf577a1f8082e807a5b909236bce35b3e3897f839f2fbd9ae6982d"}, + {file = "grpcio-1.65.4-cp310-cp310-win32.whl", hash = "sha256:66bb051881c84aa82e4f22d8ebc9d1704b2e35d7867757f0740c6ef7b902f9b1"}, + {file = "grpcio-1.65.4-cp310-cp310-win_amd64.whl", hash = "sha256:870370524eff3144304da4d1bbe901d39bdd24f858ce849b7197e530c8c8f2ec"}, + {file = "grpcio-1.65.4-cp311-cp311-linux_armv7l.whl", hash = "sha256:85e9c69378af02e483bc626fc19a218451b24a402bdf44c7531e4c9253fb49ef"}, + {file = "grpcio-1.65.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2bd672e005afab8bf0d6aad5ad659e72a06dd713020554182a66d7c0c8f47e18"}, + {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:abccc5d73f5988e8f512eb29341ed9ced923b586bb72e785f265131c160231d8"}, + {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:886b45b29f3793b0c2576201947258782d7e54a218fe15d4a0468d9a6e00ce17"}, + {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be952436571dacc93ccc7796db06b7daf37b3b56bb97e3420e6503dccfe2f1b4"}, + {file = "grpcio-1.65.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8dc9ddc4603ec43f6238a5c95400c9a901b6d079feb824e890623da7194ff11e"}, + {file = "grpcio-1.65.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ade1256c98cba5a333ef54636095f2c09e6882c35f76acb04412f3b1aa3c29a5"}, + {file = "grpcio-1.65.4-cp311-cp311-win32.whl", hash = "sha256:280e93356fba6058cbbfc6f91a18e958062ef1bdaf5b1caf46c615ba1ae71b5b"}, + {file = "grpcio-1.65.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2b819f9ee27ed4e3e737a4f3920e337e00bc53f9e254377dd26fc7027c4d558"}, + {file = "grpcio-1.65.4-cp312-cp312-linux_armv7l.whl", hash = "sha256:926a0750a5e6fb002542e80f7fa6cab8b1a2ce5513a1c24641da33e088ca4c56"}, + {file = "grpcio-1.65.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2a1d4c84d9e657f72bfbab8bedf31bdfc6bfc4a1efb10b8f2d28241efabfaaf2"}, + {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:17de4fda50967679677712eec0a5c13e8904b76ec90ac845d83386b65da0ae1e"}, + {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dee50c1b69754a4228e933696408ea87f7e896e8d9797a3ed2aeed8dbd04b74"}, + {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74c34fc7562bdd169b77966068434a93040bfca990e235f7a67cdf26e1bd5c63"}, + {file = "grpcio-1.65.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:24a2246e80a059b9eb981e4c2a6d8111b1b5e03a44421adbf2736cc1d4988a8a"}, + {file = "grpcio-1.65.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18c10f0d054d2dce34dd15855fcca7cc44ec3b811139437543226776730c0f28"}, + {file = "grpcio-1.65.4-cp312-cp312-win32.whl", hash = "sha256:d72962788b6c22ddbcdb70b10c11fbb37d60ae598c51eb47ec019db66ccfdff0"}, + {file = "grpcio-1.65.4-cp312-cp312-win_amd64.whl", hash = "sha256:7656376821fed8c89e68206a522522317787a3d9ed66fb5110b1dff736a5e416"}, + {file = "grpcio-1.65.4-cp38-cp38-linux_armv7l.whl", hash = "sha256:4934077b33aa6fe0b451de8b71dabde96bf2d9b4cb2b3187be86e5adebcba021"}, + {file = "grpcio-1.65.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0cef8c919a3359847c357cb4314e50ed1f0cca070f828ee8f878d362fd744d52"}, + {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:a925446e6aa12ca37114840d8550f308e29026cdc423a73da3043fd1603a6385"}, + {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf53e6247f1e2af93657e62e240e4f12e11ee0b9cef4ddcb37eab03d501ca864"}, + {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdb34278e4ceb224c89704cd23db0d902e5e3c1c9687ec9d7c5bb4c150f86816"}, + {file = "grpcio-1.65.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e6cbdd107e56bde55c565da5fd16f08e1b4e9b0674851d7749e7f32d8645f524"}, + {file = "grpcio-1.65.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:626319a156b1f19513156a3b0dbfe977f5f93db63ca673a0703238ebd40670d7"}, + {file = "grpcio-1.65.4-cp38-cp38-win32.whl", hash = "sha256:3d1bbf7e1dd1096378bd83c83f554d3b93819b91161deaf63e03b7022a85224a"}, + {file = "grpcio-1.65.4-cp38-cp38-win_amd64.whl", hash = "sha256:a99e6dffefd3027b438116f33ed1261c8d360f0dd4f943cb44541a2782eba72f"}, + {file = "grpcio-1.65.4-cp39-cp39-linux_armv7l.whl", hash = "sha256:874acd010e60a2ec1e30d5e505b0651ab12eb968157cd244f852b27c6dbed733"}, + {file = "grpcio-1.65.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b07f36faf01fca5427d4aa23645e2d492157d56c91fab7e06fe5697d7e171ad4"}, + {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:b81711bf4ec08a3710b534e8054c7dcf90f2edc22bebe11c1775a23f145595fe"}, + {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88fcabc332a4aef8bcefadc34a02e9ab9407ab975d2c7d981a8e12c1aed92aa1"}, + {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9ba3e63108a8749994f02c7c0e156afb39ba5bdf755337de8e75eb685be244b"}, + {file = "grpcio-1.65.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8eb485801957a486bf5de15f2c792d9f9c897a86f2f18db8f3f6795a094b4bb2"}, + {file = "grpcio-1.65.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075f3903bc1749ace93f2b0664f72964ee5f2da5c15d4b47e0ab68e4f442c257"}, + {file = "grpcio-1.65.4-cp39-cp39-win32.whl", hash = "sha256:0a0720299bdb2cc7306737295d56e41ce8827d5669d4a3cd870af832e3b17c4d"}, + {file = "grpcio-1.65.4-cp39-cp39-win_amd64.whl", hash = "sha256:a146bc40fa78769f22e1e9ff4f110ef36ad271b79707577bf2a31e3e931141b9"}, + {file = "grpcio-1.65.4.tar.gz", hash = "sha256:2a4f476209acffec056360d3e647ae0e14ae13dcf3dfb130c227ae1c594cbe39"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.64.0)"] +protobuf = ["grpcio-tools (>=1.65.4)"] [[package]] name = "grpcio-status" -version = "1.62.2" +version = "1.62.3" description = "Status proto mapping for gRPC" optional = false python-versions = ">=3.6" files = [ - {file = "grpcio-status-1.62.2.tar.gz", hash = "sha256:62e1bfcb02025a1cd73732a2d33672d3e9d0df4d21c12c51e0bbcaf09bab742a"}, - {file = "grpcio_status-1.62.2-py3-none-any.whl", hash = "sha256:206ddf0eb36bc99b033f03b2c8e95d319f0044defae9b41ae21408e7e0cda48f"}, + {file = "grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485"}, + {file = "grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8"}, ] [package.dependencies] googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.62.2" +grpcio = ">=1.62.3" protobuf = ">=4.21.6" [[package]] name = "gunicorn" -version = "21.2.0" +version = "22.0.0" description = "WSGI HTTP Server for UNIX" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, - {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, + {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"}, + {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"}, ] [package.dependencies] packaging = "*" [package.extras] -eventlet = ["eventlet (>=0.24.1)"] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] gevent = ["gevent (>=1.4.0)"] setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] tornado = ["tornado (>=0.2)"] [[package]] @@ -1308,13 +1336,13 @@ python-dateutil = "*" [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -1374,13 +1402,13 @@ tests = ["codecov", "coverage", "flake8", "flake8-quotes", "flake8-typing-import [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -1925,13 +1953,13 @@ pyasn1 = ">=0.4.6,<0.6.0" [[package]] name = "pycodestyle" -version = "2.11.1" +version = "2.12.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, - {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, ] [[package]] @@ -2003,17 +2031,17 @@ files = [ [[package]] name = "pylint" -version = "3.2.2" +version = "3.2.6" description = "python code static checker" optional = false python-versions = ">=3.8.0" files = [ - {file = "pylint-3.2.2-py3-none-any.whl", hash = "sha256:3f8788ab20bb8383e06dd2233e50f8e08949cfd9574804564803441a4946eab4"}, - {file = "pylint-3.2.2.tar.gz", hash = "sha256:d068ca1dfd735fb92a07d33cb8f288adc0f6bc1287a139ca2425366f7cbe38f8"}, + {file = "pylint-3.2.6-py3-none-any.whl", hash = "sha256:03c8e3baa1d9fb995b12c1dbe00aa6c4bcef210c2a2634374aedeb22fb4a8f8f"}, + {file = "pylint-3.2.6.tar.gz", hash = "sha256:a5d01678349454806cff6d886fb072294f56a58c4761278c97fb557d708e1eb3"}, ] [package.dependencies] -astroid = ">=3.2.2,<=3.3.0-dev0" +astroid = ">=3.2.4,<=3.3.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = {version = ">=0.3.7", markers = "python_version >= \"3.12\""} isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" @@ -2109,20 +2137,20 @@ files = [ [[package]] name = "pytest" -version = "8.2.1" +version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"}, - {file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" +pluggy = ">=1.5,<2" [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] @@ -2277,7 +2305,7 @@ jaeger-client = "*" type = "git" url = "https://github.com/bcgov/sbc-common-components.git" reference = "HEAD" -resolved_reference = "e770b4ab496e044d292500e62bc19a17079a73ec" +resolved_reference = "22978d810dc4e85c51c3129936686b0a17124e64" subdirectory = "python" [[package]] @@ -2307,38 +2335,47 @@ files = [ [[package]] name = "sentry-sdk" -version = "1.41.0" +version = "2.13.0" description = "Python client for Sentry (https://sentry.io)" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "sentry-sdk-1.41.0.tar.gz", hash = "sha256:4f2d6c43c07925d8cd10dfbd0970ea7cb784f70e79523cca9dbcd72df38e5a46"}, - {file = "sentry_sdk-1.41.0-py2.py3-none-any.whl", hash = "sha256:be4f8f4b29a80b6a3b71f0f31487beb9e296391da20af8504498a328befed53f"}, + {file = "sentry_sdk-2.13.0-py2.py3-none-any.whl", hash = "sha256:6beede8fc2ab4043da7f69d95534e320944690680dd9a963178a49de71d726c6"}, + {file = "sentry_sdk-2.13.0.tar.gz", hash = "sha256:8d4a576f7a98eb2fdb40e13106e41f330e5c79d72a68be1316e7852cf4995260"}, ] [package.dependencies] +blinker = {version = ">=1.1", optional = true, markers = "extra == \"flask\""} certifi = "*" -urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} +flask = {version = ">=0.11", optional = true, markers = "extra == \"flask\""} +markupsafe = {version = "*", optional = true, markers = "extra == \"flask\""} +urllib3 = ">=1.26.11" [package.extras] aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] arq = ["arq (>=0.23)"] asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] chalice = ["chalice (>=1.16.0)"] clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] -grpcio = ["grpcio (>=1.21.1)"] +grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] +huggingface-hub = ["huggingface-hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] +litestar = ["litestar (>=2.0.0)"] loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] -opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +opentelemetry-experimental = ["opentelemetry-distro"] pure-eval = ["asttokens", "executing", "pure-eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] @@ -2348,22 +2385,23 @@ sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] -tornado = ["tornado (>=5)"] +tornado = ["tornado (>=6)"] [[package]] name = "setuptools" -version = "70.0.0" +version = "73.0.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, - {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, + {file = "setuptools-73.0.1-py3-none-any.whl", hash = "sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e"}, + {file = "setuptools-73.0.1.tar.gz", hash = "sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "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] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.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-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] [[package]] name = "simple-cloudevent" @@ -2416,10 +2454,10 @@ develop = false [package.source] type = "git" -url = "https://github.com/bcgov/lear.git" -reference = "feature-legal-name" -resolved_reference = "e5a432d1460dc84208465ef35c0c81ab02e66f51" -subdirectory = "python/common/sql-versioning" +url = "https://github.com/bcgov/sbc-connect-common.git" +reference = "main" +resolved_reference = "c898988d239dc261b2b186465a1887f15512c102" +subdirectory = "python/sql-versioning" [[package]] name = "sqlalchemy" @@ -2580,33 +2618,33 @@ twisted = ["twisted"] [[package]] name = "tomlkit" -version = "0.12.5" +version = "0.13.2" description = "Style preserving TOML library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"}, - {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, ] [[package]] name = "tornado" -version = "6.4" +version = "6.4.1" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false -python-versions = ">= 3.8" +python-versions = ">=3.8" files = [ - {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, - {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"}, - {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"}, - {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, - {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, ] [[package]] @@ -2639,13 +2677,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "werkzeug" -version = "3.0.1" +version = "3.0.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, - {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, + {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, + {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, ] [package.dependencies] @@ -2760,4 +2798,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "ff815410d39278dd9075a51a391b967f2a71cf641585586a12682b685075ada1" +content-hash = "9a9f38109eaa6f93fc844f3ca467751cabbdccce610af17b87e7b178be02d231" diff --git a/pay-api/pre-hook-update-db.sh b/pay-api/pre-hook-update-db.sh new file mode 100644 index 000000000..47c9c3318 --- /dev/null +++ b/pay-api/pre-hook-update-db.sh @@ -0,0 +1,4 @@ +#! /bin/sh +cd /opt/app-root +echo 'starting upgrade' +poetry run flask db upgrade \ No newline at end of file diff --git a/pay-api/pyproject.toml b/pay-api/pyproject.toml index a481f9f67..e94a3ee76 100644 --- a/pay-api/pyproject.toml +++ b/pay-api/pyproject.toml @@ -8,30 +8,30 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.12" flask-caching = "2.3.0" -flask-cors = "4.0.0" +flask-cors = "5.0.0" flask-migrate = "4.0.7" flask-moment = "1.0.5" flask-sqlalchemy = "3.1.1" flask-script = "2.0.6" flask = "3.0.2" -jinja2 = "3.1.3" +jinja2 = "3.1.4" mako = "1.3.2" markupsafe = "2.1.5" sqlalchemy-utils = "0.41.1" sqlalchemy = "2.0.28" -werkzeug = "3.0.1" +werkzeug = "3.0.3" alembic = "1.13.1" attrs = "23.2.0" blinker = "1.7.0" cachelib = "0.9.0" cachetools = "5.3.3" cattrs = "23.2.3" -certifi = "2024.2.2" +certifi = "2024.7.4" cffi = "1.16.0" charset-normalizer = "3.3.2" click = "8.1.7" croniter = "2.0.2" -cryptography = "42.0.5" +cryptography = "43.0.1" dpath = "2.1.6" ecdsa = "0.18.0" expiringdict = "1.2.2" @@ -39,9 +39,9 @@ flask-jwt-oidc = {git = "https://github.com/seeker25/flask-jwt-oidc.git"} flask-marshmallow = "1.2.0" gcp-queue = { git = "https://github.com/bcgov/sbc-connect-common.git", subdirectory = "python/gcp-queue", branch = "main" } greenlet = "3.0.3" -gunicorn = "21.2.0" +gunicorn = "22.0.0" holidays = "0.37" -idna = "3.6" +idna = "3.7" itsdangerous = "2.1.2" jaeger-client = "4.8.0" jsonschema = "4.17.3" @@ -67,17 +67,18 @@ pytz = "2024.1" requests = "2.32.2" rsa = "4.9" semver = "3.0.2" -sentry-sdk = "1.41.0" +sentry-sdk = {extras = ["flask"], version = "^2.8.0"} six = "1.16.0" threadloop = "1.0.2" thrift = "0.16.0" -tornado = "6.4" +tornado = "6.4.1" typing-extensions = "4.10.0" urllib3 = "2.2.2" sbc-common-components = {git = "https://github.com/bcgov/sbc-common-components.git", subdirectory = "python"} pg8000 = "^1.30.5" -sql-versioning = { git = "https://github.com/bcgov/lear.git", subdirectory = "python/common/sql-versioning", branch = "feature-legal-name" } +sql-versioning = { git = "https://github.com/bcgov/sbc-connect-common.git", subdirectory = "python/sql-versioning", branch = "main" } aiohttp = "^3.9.5" +setuptools = "^73.0.1" [tool.poetry.group.dev.dependencies] diff --git a/pay-api/src/pay_api/__init__.py b/pay-api/src/pay_api/__init__.py index 019570dd5..64557654d 100755 --- a/pay-api/src/pay_api/__init__.py +++ b/pay-api/src/pay_api/__init__.py @@ -51,14 +51,16 @@ def create_app(run_mode=os.getenv('DEPLOYMENT_ENV', 'production')): db.init_app(app) queue.init_app(app) if run_mode != 'testing': - Migrate(app, db) - app.logger.info('Running migration upgrade.') - with app.app_context(): - execute_migrations(app) - - # Alembic has it's own logging config, we'll need to restore our logging here. - setup_logging(os.path.join(_Config.PROJECT_ROOT, 'logging.conf')) - app.logger.info('Finished migration upgrade.') + if app.config.get('CLOUD_PLATFORM') != 'OCP': + Migrate(app, db) + app.logger.info('Running migration upgrade.') + with app.app_context(): + execute_migrations(app) + # Alembic has it's own logging config, we'll need to restore our logging here. + setup_logging(os.path.join(_Config.PROJECT_ROOT, 'logging.conf')) + app.logger.info('Finished migration upgrade.') + else: + app.logger.info('Migrations were executed on prehook.') ma.init_app(app) endpoints.init_app(app) diff --git a/pay-api/src/pay_api/config.py b/pay-api/src/pay_api/config.py index be1cd05c5..9f9ab8818 100755 --- a/pay-api/src/pay_api/config.py +++ b/pay-api/src/pay_api/config.py @@ -68,6 +68,7 @@ class _Config: # pylint: disable=too-few-public-methods """Base class configuration that should set reasonable defaults for all the other configurations.""" PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) + CLOUD_PLATFORM = os.getenv('CLOUD_PLATFORM', 'OCP') SECRET_KEY = 'a secret' @@ -138,6 +139,7 @@ class _Config: # pylint: disable=too-few-public-methods BUSINESS_PAY_TOPIC = os.getenv('BUSINESS_PAY_TOPIC', 'business-pay-dev') GCP_AUTH_KEY = os.getenv('AUTHPAY_GCP_AUTH_KEY', None) NAMEX_PAY_TOPIC = os.getenv('NAMEX_PAY_TOPIC', 'namex-pay-dev') + STRR_PAY_TOPIC = os.getenv('STRR_PAY_TOPIC', BUSINESS_PAY_TOPIC) # API Endpoints AUTH_API_URL = os.getenv('AUTH_API_URL', '') diff --git a/pay-api/src/pay_api/factory/payment_system_factory.py b/pay-api/src/pay_api/factory/payment_system_factory.py index 0b1b21990..d1da4a93d 100644 --- a/pay-api/src/pay_api/factory/payment_system_factory.py +++ b/pay-api/src/pay_api/factory/payment_system_factory.py @@ -27,7 +27,6 @@ from pay_api.services.pad_service import PadService from pay_api.services.paybc_service import PaybcService from pay_api.services.payment_account import PaymentAccount -from pay_api.services.wire_service import WireService from pay_api.utils.enums import CfsAccountStatus, PaymentMethod, Role # noqa: I001 from pay_api.utils.errors import Error from pay_api.utils.user_context import UserContext, user_context @@ -58,8 +57,6 @@ def create_from_payment_method(payment_method: str): _instance = PadService() elif payment_method == PaymentMethod.EFT.value: _instance = EftService() - elif payment_method == PaymentMethod.WIRE.value: - _instance = WireService() elif payment_method == PaymentMethod.EJV.value: _instance = EjvPayService() diff --git a/pay-api/src/pay_api/models/__init__.py b/pay-api/src/pay_api/models/__init__.py index 73617ed26..18ccd1689 100755 --- a/pay-api/src/pay_api/models/__init__.py +++ b/pay-api/src/pay_api/models/__init__.py @@ -32,6 +32,7 @@ from .eft_file import EFTFile from .eft_process_status_code import EFTProcessStatusCode from .eft_short_names import EFTShortnames, EFTShortnameSchema, EFTShortnameSummarySchema +from .eft_short_names_historical import EFTShortnamesHistorical from .eft_refund import EFTRefund from .eft_short_name_links import EFTShortnameLinks, EFTShortnameLinkSchema from .eft_transaction import EFTTransaction, EFTTransactionSchema @@ -47,7 +48,7 @@ from .invoice_reference_status_code import InvoiceReferenceStatusCode, InvoiceReferenceStatusCodeSchema from .invoice_status_code import InvoiceStatusCode, InvoiceStatusCodeSchema from .line_item_status_code import LineItemStatusCode, LineItemStatusCodeSchema -from .non_sufficient_funds import NonSufficientFundsModel, NonSufficientFundsSchema +from .non_sufficient_funds import NonSufficientFunds, NonSufficientFundsSchema from .notification_status_code import NotificationStatusCode, NotificationStatusCodeSchema from .partner_disbursements import PartnerDisbursements from .payment import Payment, PaymentSchema @@ -61,8 +62,8 @@ from .refunds_partial import RefundPartialLine, RefundsPartial from .routing_slip import RoutingSlip, RoutingSlipSchema from .routing_slip_status_code import RoutingSlipStatusCode, RoutingSlipStatusCodeSchema -from .statement import Statement, StatementSchema -from .statement_invoices import StatementInvoices, StatementInvoicesSchema +from .statement import StatementDTO, Statement, StatementSchema +from .statement_invoices import StatementInvoices, StatementInvoicesSchema # noqa: I005 from .statement_recipients import StatementRecipients, StatementRecipientsSchema from .statement_settings import StatementSettings, StatementSettingsSchema from .transaction_status_code import TransactionStatusCode, TransactionStatusCodeSchema diff --git a/pay-api/src/pay_api/models/audit.py b/pay-api/src/pay_api/models/audit.py index 5845ac386..28fa0cc67 100644 --- a/pay-api/src/pay_api/models/audit.py +++ b/pay-api/src/pay_api/models/audit.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Base class for audit model.""" -from datetime import datetime +from datetime import datetime, timezone from marshmallow import fields from sqlalchemy.ext.declarative import declared_attr @@ -27,8 +27,8 @@ class Audit(BaseModel): # pylint: disable=too-few-public-methods __abstract__ = True - created_on = db.Column('created_on', db.DateTime, nullable=False, default=datetime.now) - updated_on = db.Column('updated_on', db.DateTime, default=None, onupdate=datetime.now) + created_on = db.Column('created_on', db.DateTime, nullable=False, default=lambda: datetime.now(tz=timezone.utc)) + updated_on = db.Column('updated_on', db.DateTime, default=None, onupdate=lambda: datetime.now(tz=timezone.utc)) @declared_attr def created_by(cls): # pylint:disable=no-self-argument, # noqa: N805 diff --git a/pay-api/src/pay_api/models/cas_settlement.py b/pay-api/src/pay_api/models/cas_settlement.py index 810aa69fb..82519ef81 100644 --- a/pay-api/src/pay_api/models/cas_settlement.py +++ b/pay-api/src/pay_api/models/cas_settlement.py @@ -13,7 +13,7 @@ # limitations under the License. """Class to record the settlement file information for a payment.""" -from datetime import datetime +from datetime import datetime, timezone from pay_api.models.base_model import BaseModel from .db import db @@ -43,6 +43,6 @@ class CasSettlement(BaseModel): # pylint: disable=too-few-public-methods } id = db.Column(db.Integer, primary_key=True, autoincrement=True) - received_on = db.Column('received_on', db.DateTime, nullable=False, default=datetime.now) + received_on = db.Column('received_on', db.DateTime, nullable=False, default=lambda: datetime.now(tz=timezone.utc)) file_name = db.Column(db.String, nullable=False) processed_on = db.Column('processed_on', db.DateTime, nullable=True) diff --git a/pay-api/src/pay_api/models/comment.py b/pay-api/src/pay_api/models/comment.py index 1f57b9f43..c16c82567 100644 --- a/pay-api/src/pay_api/models/comment.py +++ b/pay-api/src/pay_api/models/comment.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Model to handle all operations related to Routing Slip Comment data.""" -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy.orm import relationship from sqlalchemy import ForeignKey from sqlalchemy.ext.declarative import declared_attr @@ -53,7 +53,7 @@ class Comment(BaseModel): id = db.Column(db.Integer, primary_key=True, autoincrement=True) comment = db.Column(db.String(4096)) - timestamp = db.Column('timestamp', db.DateTime(timezone=True), default=datetime.utcnow) + timestamp = db.Column('timestamp', db.DateTime(timezone=True), default=lambda: datetime.now(tz=timezone.utc)) # Parent relationship routing_slip_number = db.Column(db.String(), ForeignKey('routing_slips.number'), index=True) routing_slip = relationship(RoutingSlip, foreign_keys=[routing_slip_number], lazy='select', innerjoin=True) diff --git a/pay-api/src/pay_api/models/corp_type.py b/pay-api/src/pay_api/models/corp_type.py index 587e59cc5..67fe40db1 100644 --- a/pay-api/src/pay_api/models/corp_type.py +++ b/pay-api/src/pay_api/models/corp_type.py @@ -45,6 +45,7 @@ class CorpType(db.Model, CodeTable): 'bcol_staff_fee_code', 'code', 'description', + 'has_partner_disbursements', 'is_online_banking_allowed', 'product' ] @@ -57,6 +58,7 @@ class CorpType(db.Model, CodeTable): bcol_code_no_service_fee = db.Column(db.String(20), nullable=True) bcol_staff_fee_code = db.Column(db.String(20), nullable=True) is_online_banking_allowed = db.Column(Boolean(), default=True) + has_partner_disbursements = db.Column(Boolean(), default=False) batch_type = db.Column(db.String(2), nullable=True) product = db.Column(db.String(20), nullable=True) diff --git a/pay-api/src/pay_api/models/credit.py b/pay-api/src/pay_api/models/credit.py index d604f1e3c..6018d878a 100644 --- a/pay-api/src/pay_api/models/credit.py +++ b/pay-api/src/pay_api/models/credit.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Model to handle all operations related to Credit data.""" -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy import Boolean, ForeignKey from .base_model import BaseModel @@ -53,7 +53,7 @@ class Credit(BaseModel): # pylint:disable=too-many-instance-attributes amount = db.Column(db.Float, nullable=False) remaining_amount = db.Column(db.Float, nullable=False) details = db.Column(db.String(200), nullable=True) - created_on = db.Column('created_on', db.DateTime, nullable=True, default=datetime.now) + created_on = db.Column('created_on', db.DateTime, nullable=True, default=lambda: datetime.now(tz=timezone.utc)) account_id = db.Column(db.Integer, ForeignKey('payment_accounts.id'), nullable=True, index=True) diff --git a/pay-api/src/pay_api/models/custom_query.py b/pay-api/src/pay_api/models/custom_query.py index 7709db44f..1b18978cc 100644 --- a/pay-api/src/pay_api/models/custom_query.py +++ b/pay-api/src/pay_api/models/custom_query.py @@ -21,6 +21,14 @@ class CustomQuery(Query): # pylint: disable=too-many-ancestors """Custom Query class to extend the base query class for helper functionality.""" + def filter_boolean(self, search_criteria, model_attribute): + """Add query filter for boolean value.""" + if search_criteria is False: + return self + if search_criteria is None: + raise ValueError('Invalid search criteria None, not True or False') + return self.filter(model_attribute == search_criteria) + def filter_conditionally(self, search_criteria, model_attribute, is_like: bool = False): """Add query filter if present.""" if search_criteria is None: diff --git a/pay-api/src/pay_api/models/distribution_code.py b/pay-api/src/pay_api/models/distribution_code.py index f0a386306..02528ea1f 100644 --- a/pay-api/src/pay_api/models/distribution_code.py +++ b/pay-api/src/pay_api/models/distribution_code.py @@ -14,7 +14,7 @@ """Model to handle all operations related to distribution code.""" from __future__ import annotations -from datetime import date +from datetime import datetime, timezone from marshmallow import fields from sql_versioning import Versioned @@ -89,7 +89,7 @@ class DistributionCode(Audit, Versioned, BaseModel): # pylint:disable=too-many- stob = db.Column(db.String(50), nullable=True) project_code = db.Column(db.String(50), nullable=True) - start_date = db.Column(db.Date, default=date.today(), nullable=False) + start_date = db.Column(db.Date, default=lambda: datetime.now(tz=timezone.utc).date(), nullable=False) end_date = db.Column(db.Date, default=None, nullable=True) stop_ejv = db.Column('stop_ejv', Boolean(), default=False) @@ -114,7 +114,7 @@ def __str__(self): @classmethod def find_all(cls, include_gov_account_gl_codes: bool = False): """Find all distribution codes.""" - valid_date = date.today() + valid_date = datetime.now(tz=timezone.utc).date() query = cls.query.filter(DistributionCode.start_date <= valid_date). \ filter((DistributionCode.end_date.is_(None)) | (DistributionCode.end_date >= valid_date)). \ order_by(DistributionCode.name.asc()) @@ -133,7 +133,7 @@ def find_by_service_fee_distribution_id(cls, service_fee_distribution_code_id): @classmethod def find_by_active_for_fee_schedule(cls, fee_schedule_id: int): """Return active distribution for fee schedule.""" - valid_date = date.today() + valid_date = datetime.now(tz=timezone.utc).date() query = db.session.query(DistributionCode). \ join(DistributionCodeLink). \ filter(DistributionCodeLink.fee_schedule_id == fee_schedule_id). \ @@ -146,7 +146,7 @@ def find_by_active_for_fee_schedule(cls, fee_schedule_id: int): @classmethod def find_by_active_for_account(cls, account_id: int): """Return active distribution for account.""" - valid_date = date.today() + valid_date = datetime.now(tz=timezone.utc).date() query = db.session.query(DistributionCode). \ filter(DistributionCode.account_id == account_id). \ filter(DistributionCode.start_date <= valid_date). \ diff --git a/pay-api/src/pay_api/models/eft_credit.py b/pay-api/src/pay_api/models/eft_credit.py index 5a8927d12..0404883be 100644 --- a/pay-api/src/pay_api/models/eft_credit.py +++ b/pay-api/src/pay_api/models/eft_credit.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Model to handle all operations related to EFT Credits data.""" -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy import ForeignKey from .base_model import BaseModel @@ -50,11 +50,10 @@ class EFTCredit(BaseModel): # pylint:disable=too-many-instance-attributes amount = db.Column(db.Numeric(19, 2), nullable=False) remaining_amount = db.Column(db.Numeric(19, 2), nullable=False) - created_on = db.Column('created_on', db.DateTime, nullable=False, default=datetime.now) + created_on = db.Column('created_on', db.DateTime, nullable=False, default=lambda: datetime.now(tz=timezone.utc)) eft_file_id = db.Column(db.Integer, ForeignKey('eft_files.id'), nullable=False) short_name_id = db.Column(db.Integer, ForeignKey('eft_short_names.id'), nullable=False) - payment_account_id = db.Column(db.Integer, ForeignKey('payment_accounts.id'), nullable=True, index=True) eft_transaction_id = db.Column(db.Integer, ForeignKey('eft_transactions.id'), nullable=True) @classmethod diff --git a/pay-api/src/pay_api/models/eft_credit_invoice_link.py b/pay-api/src/pay_api/models/eft_credit_invoice_link.py index a7f0b5342..e90efdf87 100644 --- a/pay-api/src/pay_api/models/eft_credit_invoice_link.py +++ b/pay-api/src/pay_api/models/eft_credit_invoice_link.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. """Model to link invoices with EFT Credits.""" -from datetime import datetime +from datetime import datetime, timezone -from sqlalchemy import ForeignKey +from sqlalchemy import ForeignKey, text from .base_model import BaseModel from .db import db @@ -42,6 +42,7 @@ class EFTCreditInvoiceLink(BaseModel): # pylint: disable=too-few-public-methods 'created_on', 'eft_credit_id', 'invoice_id', + 'link_group_id', 'receipt_number', 'status_code' ] @@ -52,8 +53,9 @@ class EFTCreditInvoiceLink(BaseModel): # pylint: disable=too-few-public-methods eft_credit_id = db.Column(db.Integer, ForeignKey('eft_credits.id'), nullable=False, index=True) amount = db.Column(db.Numeric(19, 2), nullable=True) status_code = db.Column('status_code', db.String(25), nullable=False, index=True) - created_on = db.Column('created_on', db.DateTime, nullable=False, default=datetime.now) + created_on = db.Column('created_on', db.DateTime, nullable=False, default=lambda: datetime.now(tz=timezone.utc)) receipt_number = db.Column(db.String(50), nullable=True) + link_group_id = db.Column(db.Integer, nullable=True) @classmethod def find_pending_invoice_links(cls, invoice_id: int): @@ -62,3 +64,14 @@ def find_pending_invoice_links(cls, invoice_id: int): .filter_by(invoice_id=invoice_id) .filter(cls.status_code.in_([EFTCreditInvoiceStatus.PENDING.value])) ).one_or_none() + + @classmethod + def find_by_invoice_id(cls, invoice_id: int): + """Find links by invoice id.""" + # Order is important here, we use it in the jobs. + return cls.query.filter_by(invoice_id=invoice_id).order_by(EFTCreditInvoiceLink.id.desc()).all() + + @classmethod + def get_next_group_link_seq(cls): + """Get next value of EFT Group Link Sequence.""" + return db.session.execute(text("SELECT nextval('eft_group_link_seq')")).scalar() diff --git a/pay-api/src/pay_api/models/eft_file.py b/pay-api/src/pay_api/models/eft_file.py index 14a971f9d..d2986dc0e 100644 --- a/pay-api/src/pay_api/models/eft_file.py +++ b/pay-api/src/pay_api/models/eft_file.py @@ -13,7 +13,7 @@ # limitations under the License. """Model to handle EFT file processing.""" -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy import ForeignKey @@ -52,7 +52,7 @@ class EFTFile(BaseModel): # pylint: disable=too-many-instance-attributes } id = db.Column(db.Integer, primary_key=True, autoincrement=True) - created_on = db.Column('created_on', db.DateTime, nullable=False, default=datetime.now) + created_on = db.Column('created_on', db.DateTime, nullable=False, default=lambda: datetime.now(tz=timezone.utc)) completed_on = db.Column('completed_on', db.DateTime, nullable=True) deposit_from_date = db.Column('deposit_from_date', db.DateTime, nullable=True) deposit_to_date = db.Column('deposit_to_date', db.DateTime, nullable=True) diff --git a/pay-api/src/pay_api/models/eft_refund.py b/pay-api/src/pay_api/models/eft_refund.py index 8d35e4d3f..6ffb1cbc6 100644 --- a/pay-api/src/pay_api/models/eft_refund.py +++ b/pay-api/src/pay_api/models/eft_refund.py @@ -11,8 +11,8 @@ # 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. -"""Model to handle EFT file processing.""" -from datetime import datetime +"""Model to handle EFT REFUNDS, this is picked up by the AP job to mail out.""" +from datetime import datetime, timezone from sqlalchemy import ForeignKey @@ -53,7 +53,7 @@ class EFTRefund(BaseModel): # pylint: disable=too-many-instance-attributes cas_supplier_number = db.Column(db.String(), nullable=False) comment = db.Column(db.String(), nullable=False) - created_on = db.Column('created_on', db.DateTime, nullable=False, default=datetime.now) + created_on = db.Column('created_on', db.DateTime, nullable=False, default=lambda: datetime.now(tz=timezone.utc)) id = db.Column(db.Integer, primary_key=True, autoincrement=True) refund_amount = db.Column(db.Numeric(), nullable=False) refund_email = db.Column(db.String(100), nullable=False) diff --git a/pay-api/src/pay_api/models/eft_refund_email_list.py b/pay-api/src/pay_api/models/eft_refund_email_list.py index 1448509e7..402889350 100644 --- a/pay-api/src/pay_api/models/eft_refund_email_list.py +++ b/pay-api/src/pay_api/models/eft_refund_email_list.py @@ -14,6 +14,8 @@ """Model to handle all operations related to Fee related to accounts.""" from __future__ import annotations +from sqlalchemy import Sequence + from .base_model import BaseModel from .db import db @@ -34,7 +36,7 @@ class EFTRefundEmailList(BaseModel): # Exception, id is always first, _fields first __mapper_args__ = { 'include_properties': [ - 'email' + 'email', 'first_name', 'id', 'last_name', @@ -43,8 +45,8 @@ class EFTRefundEmailList(BaseModel): email = db.Column(db.String(25), nullable=False) first_name = db.Column(db.String(25), nullable=True) - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - last_name = db.Column(db.String(25), nullable=False) + id = db.Column(db.Integer, Sequence('eft_refund_email_list_id_seq'), primary_key=True) + last_name = db.Column(db.String(25), nullable=True) @classmethod def find_all_emails(cls): diff --git a/pay-api/src/pay_api/models/eft_short_name_links.py b/pay-api/src/pay_api/models/eft_short_name_links.py index 228db76d9..8bac5a156 100644 --- a/pay-api/src/pay_api/models/eft_short_name_links.py +++ b/pay-api/src/pay_api/models/eft_short_name_links.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Model to handle EFT short name to BCROS account mapping links.""" -from datetime import datetime +from datetime import datetime, timezone from typing import List from _decimal import Decimal @@ -57,7 +57,7 @@ class EFTShortnameLinks(Versioned, BaseModel): # pylint: disable=too-many-insta id = db.Column(db.Integer, primary_key=True, autoincrement=True) eft_short_name_id = db.Column(db.Integer, ForeignKey('eft_short_names.id'), nullable=False, index=True) auth_account_id = db.Column('auth_account_id', db.String(50), nullable=False, index=True) - created_on = db.Column('created_on', db.DateTime, nullable=False, default=datetime.now) + created_on = db.Column('created_on', db.DateTime, nullable=False, default=lambda: datetime.now(tz=timezone.utc)) status_code = db.Column('status_code', db.String(25), nullable=False, index=True) updated_by = db.Column('updated_by', db.String(100), nullable=True) updated_by_name = db.Column('updated_by_name', db.String(100), nullable=True) @@ -89,6 +89,23 @@ def find_link_by_status(cls, short_name_id: int, auth_account_id: str, statuses: .filter(cls.status_code.in_(statuses)) ).one_or_none() + @classmethod + def get_short_name_links_count(cls, auth_account_id): + """Find short name account link by status.""" + statuses = [EFTShortnameStatus.LINKED.value, + EFTShortnameStatus.PENDING.value] + active_link = (cls.query + .filter_by(auth_account_id=auth_account_id) + .filter(cls.status_code.in_(statuses)) + ).one_or_none() + + if active_link is None: + return 0 + + return (cls.query + .filter_by(eft_short_name_id=active_link.eft_short_name_id) + .filter(cls.status_code.in_(statuses))).count() + @define class EFTShortnameLinkSchema: # pylint: disable=too-few-public-methods diff --git a/pay-api/src/pay_api/models/eft_short_names.py b/pay-api/src/pay_api/models/eft_short_names.py index e9acbc11a..630c6fd07 100644 --- a/pay-api/src/pay_api/models/eft_short_names.py +++ b/pay-api/src/pay_api/models/eft_short_names.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Model to handle EFT TDI17 short name to BCROS account mapping.""" -from datetime import datetime +from datetime import datetime, timezone from _decimal import Decimal from attrs import define @@ -46,7 +46,7 @@ class EFTShortnames(Versioned, BaseModel): # pylint: disable=too-many-instance- } id = db.Column(db.Integer, primary_key=True, autoincrement=True) - created_on = db.Column('created_on', db.DateTime, nullable=False, default=datetime.now) + created_on = db.Column('created_on', db.DateTime, nullable=False, default=lambda: datetime.now(tz=timezone.utc)) short_name = db.Column('short_name', db.String, nullable=False, index=True) @classmethod diff --git a/pay-api/src/pay_api/models/eft_short_names_historical.py b/pay-api/src/pay_api/models/eft_short_names_historical.py new file mode 100644 index 000000000..ffc55d6bd --- /dev/null +++ b/pay-api/src/pay_api/models/eft_short_names_historical.py @@ -0,0 +1,116 @@ +# Copyright © 2024 Province of British Columbia +# +# 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. +"""Model to handle all operations related to EFT Short name historical audit data.""" +from datetime import datetime, timezone +from decimal import Decimal +from typing import Self + +from attr import define +from sqlalchemy import ForeignKey + +from .base_model import BaseModel +from .db import db + + +class EFTShortnamesHistorical(BaseModel): # pylint:disable=too-many-instance-attributes + """This class manages all EFT Short name historical data.""" + + __tablename__ = 'eft_short_names_historical' + # this mapper is used so that new and old versions of the service can be run simultaneously, + # making rolling upgrades easier + # This is used by SQLAlchemy to explicitly define which fields we're interested + # so it doesn't freak out and say it can't map the structure if other fields are present. + # This could occur from a failed deploy or during an upgrade. + # The other option is to tell SQLAlchemy to ignore differences, but that is ambiguous + # and can interfere with Alembic upgrades. + # + # NOTE: please keep mapper names in alpha-order, easier to track that way + # Exception, id is always first, _fields first + __mapper_args__ = { + 'include_properties': [ + 'id', + 'amount', + 'created_by', + 'created_on', + 'credit_balance', + 'description', + 'hidden', + 'invoice_id', + 'is_processing', + 'payment_account_id', + 'related_group_link_id', + 'short_name_id', + 'statement_number', + 'transaction_date', + 'transaction_type' + ] + } + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + amount = db.Column(db.Numeric(19, 2), nullable=False) + created_by = db.Column(db.String, nullable=True) + created_on = db.Column(db.DateTime, nullable=False, default=lambda: datetime.now(tz=timezone.utc)) + credit_balance = db.Column(db.Numeric(19, 2), nullable=False) + hidden = db.Column(db.Boolean(), nullable=False, default=False, index=True) + invoice_id = db.Column(db.Integer, ForeignKey('invoices.id'), nullable=True, index=True) + is_processing = db.Column(db.Boolean(), nullable=False, default=False) + payment_account_id = db.Column(db.Integer, ForeignKey('payment_accounts.id'), nullable=True, index=True) + related_group_link_id = db.Column(db.Integer, nullable=True, index=True) + short_name_id = db.Column(db.Integer, ForeignKey('eft_short_names.id'), nullable=False) + statement_number = db.Column(db.Integer, nullable=True) + transaction_date = db.Column(db.DateTime, nullable=False, index=True) + transaction_type = db.Column(db.String, nullable=False) + + @classmethod + def find_by_related_group_link_id(cls, group_link_id: int) -> Self: + """Find historical records by related EFT Credit Invoice Link group id.""" + return cls.query.filter_by(related_group_link_id=group_link_id).one_or_none() + + +@define +class EFTShortnameHistorySchema: # pylint: disable=too-few-public-methods + """Main schema used to serialize an EFT Short name historical record.""" + + historical_id: int + account_id: str + account_name: str + account_branch: str + amount: Decimal + invoice_id: int + statement_number: int + short_name_id: int + short_name_balance: Decimal + transaction_date: datetime + transaction_type: str + is_processing: bool + is_reversible: bool + + @classmethod + def from_row(cls, row): + """From row is used so we don't tightly couple to our database class. + + https://www.attrs.org/en/stable/init.html + """ + return cls(historical_id=row.id, + short_name_id=row.short_name_id, + amount=getattr(row, 'amount', None), + short_name_balance=getattr(row, 'credit_balance', None), + account_id=getattr(row, 'auth_account_id', None), + account_name=getattr(row, 'account_name', None), + account_branch=getattr(row, 'account_branch', None), + invoice_id=getattr(row, 'invoice_id', None), + statement_number=getattr(row, 'statement_number', None), + transaction_date=getattr(row, 'transaction_date', None), + transaction_type=getattr(row, 'transaction_type', None), + is_processing=bool(getattr(row, 'is_processing', False)), + is_reversible=bool(getattr(row, 'is_reversible', False))) diff --git a/pay-api/src/pay_api/models/eft_transaction.py b/pay-api/src/pay_api/models/eft_transaction.py index 3f4fbb68b..ce3178702 100644 --- a/pay-api/src/pay_api/models/eft_transaction.py +++ b/pay-api/src/pay_api/models/eft_transaction.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Model to handle EFT file processing.""" -from datetime import datetime +from datetime import datetime, timezone from _decimal import Decimal from attrs import define @@ -63,10 +63,11 @@ class EFTTransaction(BaseModel): # pylint: disable=too-many-instance-attributes id = db.Column(db.Integer, primary_key=True, autoincrement=True) batch_number = db.Column('batch_number', db.String(10), nullable=True) completed_on = db.Column('completed_on', db.DateTime, nullable=True) - created_on = db.Column('created_on', db.DateTime, nullable=False, default=datetime.now) + created_on = db.Column('created_on', db.DateTime, nullable=False, default=lambda: datetime.now(tz=timezone.utc)) error_messages = db.Column(ARRAY(String, dimensions=1), nullable=True) file_id = db.Column(db.Integer, ForeignKey('eft_files.id'), nullable=False, index=True) - last_updated_on = db.Column('last_updated_on', db.DateTime, nullable=False, default=datetime.now) + last_updated_on = db.Column('last_updated_on', db.DateTime, nullable=False, + default=lambda: datetime.now(tz=timezone.utc)) line_number = db.Column('line_number', db.Integer, nullable=False) line_type = db.Column('line_type', db.String(), nullable=False) jv_type = db.Column('jv_type', db.String(1), nullable=True) diff --git a/pay-api/src/pay_api/models/ejv_file.py b/pay-api/src/pay_api/models/ejv_file.py index 7c44c6d8e..ec9aa07b5 100644 --- a/pay-api/src/pay_api/models/ejv_file.py +++ b/pay-api/src/pay_api/models/ejv_file.py @@ -13,7 +13,7 @@ # limitations under the License. """Model to handle Electronic Journal Voucher distributions and payment.""" -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy import ForeignKey @@ -51,7 +51,7 @@ class EjvFile(BaseModel): # pylint: disable=too-many-instance-attributes } id = db.Column(db.Integer, primary_key=True, autoincrement=True) - created_on = db.Column('created_on', db.DateTime, nullable=False, default=datetime.now) + created_on = db.Column('created_on', db.DateTime, nullable=False, default=lambda: datetime.now(tz=timezone.utc)) completed_on = db.Column('completed_on', db.DateTime, nullable=True) file_type = db.Column('file_type', db.String, nullable=True, default=EjvFileType.DISBURSEMENT.value) file_ref = db.Column('file_ref', db.String, nullable=False, index=True) diff --git a/pay-api/src/pay_api/models/fee_schedule.py b/pay-api/src/pay_api/models/fee_schedule.py index 17edfd9ef..3cf9b3757 100644 --- a/pay-api/src/pay_api/models/fee_schedule.py +++ b/pay-api/src/pay_api/models/fee_schedule.py @@ -13,7 +13,7 @@ # limitations under the License. """Model to handle all operations related to fee and fee schedule.""" -from datetime import date, datetime +from datetime import datetime, timezone from operator import or_ from sqlalchemy import Boolean, Date, ForeignKey, cast, func @@ -65,7 +65,8 @@ class FeeSchedule(db.Model): filing_type_code = db.Column(db.String(10), ForeignKey('filing_types.code'), nullable=False) corp_type_code = db.Column(db.String(10), ForeignKey('corp_types.code'), nullable=False) fee_code = db.Column(db.String(10), ForeignKey('fee_codes.code'), nullable=False) - fee_start_date = db.Column('fee_start_date', db.Date, default=date.today(), nullable=False) + fee_start_date = db.Column('fee_start_date', db.Date, default=lambda: datetime.now(tz=timezone.utc).date(), + nullable=False) fee_end_date = db.Column('fee_end_date', db.Date, default=None, nullable=True) future_effective_fee_code = db.Column(db.String(10), ForeignKey('fee_codes.code'), nullable=True) priority_fee_code = db.Column(db.String(10), ForeignKey('fee_codes.code'), nullable=True) @@ -98,7 +99,7 @@ def find_by_filing_type_and_corp_type(cls, corp_type_code: str, ): """Given a filing_type_code and corp_type, this will return fee schedule.""" if not valid_date: - valid_date = date.today() + valid_date = datetime.now(tz=timezone.utc).date() fee_schedule = None if filing_type_code and corp_type_code: @@ -119,7 +120,7 @@ def find_by_id(cls, fee_schedule_id: int): @classmethod def find_all(cls, corp_type_code: str = None, filing_type_code: str = None, description: str = None): """Find all fee schedules matching the filters.""" - valid_date = date.today() + valid_date = datetime.now(tz=timezone.utc).date() query = cls.query.filter(FeeSchedule.fee_start_date <= valid_date). \ filter((FeeSchedule.fee_end_date.is_(None)) | (FeeSchedule.fee_end_date >= valid_date)) diff --git a/pay-api/src/pay_api/models/invoice.py b/pay-api/src/pay_api/models/invoice.py index d5e571f00..5f0663799 100644 --- a/pay-api/src/pay_api/models/invoice.py +++ b/pay-api/src/pay_api/models/invoice.py @@ -14,9 +14,10 @@ """Model to handle all operations related to Invoice.""" from __future__ import annotations -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from decimal import Decimal from typing import List, Optional +import pytz from dateutil.relativedelta import relativedelta from attrs import define @@ -37,6 +38,18 @@ from .receipt import ReceiptSchema +def determine_overdue_date(context): + """Determine the overdue date with the correct time offset.""" + created_on = context.get_current_parameters()['created_on'] + target_date = created_on.date() + relativedelta(months=2, + day=15) + target_datetime = datetime.combine(target_date, datetime.min.time()) + # Correct for daylight savings. + hours = target_datetime.astimezone(pytz.timezone('America/Vancouver')).utcoffset().total_seconds() / 60 / 60 + target_date = target_datetime.replace(tzinfo=timezone.utc) + relativedelta(hours=-hours) + return target_date.replace(tzinfo=None) + + class Invoice(Audit): # pylint: disable=too-many-instance-attributes """This class manages all of the base data about Invoice.""" @@ -63,6 +76,7 @@ class Invoice(Audit): # pylint: disable=too-many-instance-attributes 'cfs_account_id', 'dat_number', 'details', + 'disbursement_reversal_date', 'disbursement_status_code', 'disbursement_date', 'filing_id', @@ -86,7 +100,7 @@ class Invoice(Audit): # pylint: disable=too-many-instance-attributes id = db.Column(db.Integer, primary_key=True, autoincrement=True) - invoice_status_code = db.Column(db.String(20), ForeignKey('invoice_status_codes.code'), nullable=False) + invoice_status_code = db.Column(db.String(20), ForeignKey('invoice_status_codes.code'), nullable=False, index=True) payment_account_id = db.Column(db.Integer, ForeignKey('payment_accounts.id'), nullable=True, index=True) cfs_account_id = db.Column(db.Integer, ForeignKey('cfs_accounts.id'), nullable=True) payment_method_code = db.Column(db.String(15), ForeignKey('payment_methods.code'), nullable=False, index=True) @@ -94,15 +108,14 @@ class Invoice(Audit): # pylint: disable=too-many-instance-attributes disbursement_status_code = db.Column(db.String(20), ForeignKey('disbursement_status_codes.code'), nullable=True) disbursement_date = db.Column(db.DateTime, nullable=True) disbursement_reversal_date = db.Column(db.DateTime, nullable=True) - created_on = db.Column('created_on', db.DateTime, nullable=False, default=datetime.now, index=True) + created_on = db.Column('created_on', db.DateTime, nullable=False, default=lambda: datetime.now(tz=timezone.utc), + index=True) business_identifier = db.Column(db.String(20), nullable=True) total = db.Column(db.Numeric(19, 2), nullable=False) paid = db.Column(db.Numeric(19, 2), nullable=True) payment_date = db.Column(db.DateTime, nullable=True) - # default overdue_date to the first of next month - overdue_date = db.Column(db.DateTime, nullable=True, - default=lambda: datetime.now(tz=timezone.utc) + relativedelta(months=1, day=1)) + overdue_date = db.Column(db.DateTime, nullable=True, default=determine_overdue_date) refund_date = db.Column(db.DateTime, nullable=True) refund = db.Column(db.Numeric(19, 2), nullable=True) routing_slip = db.Column(db.String(50), nullable=True, index=True) @@ -145,10 +158,11 @@ def find_by_business_identifier(cls, business_identifier: str): return cls.query.filter_by(business_identifier=business_identifier).all() @classmethod - def find_invoices_by_status_for_account(cls, pay_account_id: int, invoice_statuses: List[str]): + def find_invoices_by_status_for_account(cls, pay_account_id: int, invoice_statuses: List[str]) -> List[Invoice]: """Return invoices by status for an account.""" - query = cls.query.filter_by(payment_account_id=pay_account_id). \ - filter(Invoice.invoice_status_code.in_(invoice_statuses)) + query = cls.query.filter_by(payment_account_id=pay_account_id) \ + .filter(Invoice.invoice_status_code.in_(invoice_statuses)) \ + .order_by(Invoice.id) return query.all() @@ -187,7 +201,7 @@ def find_outstanding_invoices_for_account(cls, pay_account_id: int, from_date: d ) | ( (Invoice.payment_method_code == PaymentMethod.EFT.value) & - (Invoice.payment_method_code.in_([InvoiceStatus.CREATED.value, InvoiceStatus.OVERDUE.value]) + (Invoice.payment_method_code.in_([InvoiceStatus.APPROVED.value, InvoiceStatus.OVERDUE.value]) ) & (Invoice.created_on >= from_date) ) @@ -195,6 +209,19 @@ def find_outstanding_invoices_for_account(cls, pay_account_id: int, from_date: d return query.all() + @classmethod + def find_created_direct_pay_invoices(cls, days: int = 0, hours: int = 0, minutes: int = 0): + """Return recent invoices within a certain time and is not complete. + + Used in the batch job to find orphan records which are untouched for a time. + Removed CC payments cause CC use the get receipt route, not the PAYBC invoice status route + """ + earliest_transaction_time = datetime.now(tz=timezone.utc) - (timedelta(days=days, hours=hours, minutes=minutes)) + return db.session.query(Invoice) \ + .filter(Invoice.invoice_status_code == InvoiceStatus.CREATED.value) \ + .filter(Invoice.payment_method_code.in_([PaymentMethod.DIRECT_PAY.value])) \ + .filter(Invoice.created_on >= earliest_transaction_time).all() + class InvoiceSchema(AuditSchema, BaseSchema): # pylint: disable=too-many-ancestors """Main schema used to serialize the invoice.""" @@ -279,6 +306,8 @@ class InvoiceSearchModel: # pylint: disable=too-few-public-methods, too-many-in payment_date: datetime refund_date: datetime disbursement_date: datetime + disbursement_reversal_date: datetime + # Add disbursement_reversal_date when CSO is prepared. @classmethod def from_row(cls, row): @@ -287,6 +316,7 @@ def from_row(cls, row): https://www.attrs.org/en/stable/init.html """ # Similar to _clean_up in InvoiceSchema. + # In the future may need to add a mapping from EFT Status: APPROVED -> COMPLETED status_code = PaymentStatus.COMPLETED.value if row.invoice_status_code == InvoiceStatus.PAID.value \ else row.invoice_status_code business_identifier = None if row.business_identifier and row.business_identifier.startswith('T') \ @@ -307,4 +337,5 @@ def from_row(cls, row): payment_date=row.payment_date, refund_date=row.refund_date, disbursement_date=row.disbursement_date, + disbursement_reversal_date=row.disbursement_reversal_date, invoice_number=invoice_number) diff --git a/pay-api/src/pay_api/models/invoice_reference.py b/pay-api/src/pay_api/models/invoice_reference.py index 369205b5d..1ba566d4f 100644 --- a/pay-api/src/pay_api/models/invoice_reference.py +++ b/pay-api/src/pay_api/models/invoice_reference.py @@ -13,6 +13,8 @@ # limitations under the License. """Model to handle invoice references from third party systems.""" from __future__ import annotations +from datetime import datetime, timezone +from typing import List from marshmallow import fields from sqlalchemy import ForeignKey @@ -41,8 +43,10 @@ class InvoiceReference(BaseModel): # pylint: disable=too-many-instance-attribut __mapper_args__ = { 'include_properties': [ 'id', + 'created_on', 'invoice_id', 'invoice_number', + 'is_consolidated', 'reference_number', 'status_code' ] @@ -50,6 +54,8 @@ class InvoiceReference(BaseModel): # pylint: disable=too-many-instance-attribut id = db.Column(db.Integer, primary_key=True, autoincrement=True) invoice_id = db.Column(db.Integer, ForeignKey('invoices.id'), nullable=False, index=True) + created_on = db.Column(db.DateTime, nullable=True, default=lambda: datetime.now(tz=timezone.utc)) + is_consolidated = db.Column(db.Boolean, nullable=False, default=False, server_default='f', index=True) invoice_number = db.Column(db.String(50), nullable=True, index=True) reference_number = db.Column(db.String(50), nullable=True) @@ -57,9 +63,15 @@ class InvoiceReference(BaseModel): # pylint: disable=too-many-instance-attribut 'invoice_reference_status_codes.code'), nullable=False, index=True) @classmethod - def find_by_invoice_id_and_status(cls, invoice_id: int, status_code: str) -> InvoiceReference: - """Return active Invoice Reference by invoice id.""" - return cls.query.filter_by(invoice_id=invoice_id).filter_by(status_code=status_code).one_or_none() + def find_by_invoice_id_and_status(cls, invoice_id: int, status_code: str, exclude_consolidated=False) \ + -> InvoiceReference: + """Return Invoice Reference by invoice id by status_code.""" + query = cls.query.filter_by(invoice_id=invoice_id).filter_by(status_code=status_code) + if exclude_consolidated: + query = query.filter(InvoiceReference.is_consolidated.is_(False)) + if status_code == InvoiceReferenceStatus.CANCELLED.value: + return query.order_by(InvoiceReference.id.desc()).first() + return query.one_or_none() @classmethod def find_any_active_reference_by_invoice_number(cls, invoice_number: str) -> InvoiceReference: @@ -67,6 +79,23 @@ def find_any_active_reference_by_invoice_number(cls, invoice_number: str) -> Inv return cls.query.filter_by(invoice_number=invoice_number) \ .filter_by(status_code=InvoiceReferenceStatus.ACTIVE.value).first() + @classmethod + def find_non_consolidated_invoice_numbers(cls, invoice_number: str) -> List[str]: + """Find the original invoice numbers that are not consolidated.""" + consolidated_invoice_references = db.session.query(InvoiceReference.invoice_id) \ + .filter(InvoiceReference.invoice_number == invoice_number) \ + .filter(InvoiceReference.is_consolidated.is_(True)) \ + .filter(InvoiceReference.status_code == InvoiceReferenceStatus.COMPLETED.value) \ + .distinct(InvoiceReference.invoice_id) + + original_invoice_references = db.session.query(InvoiceReference.invoice_number) \ + .filter(InvoiceReference.is_consolidated.is_(False)) \ + .filter(InvoiceReference.status_code == InvoiceReferenceStatus.CANCELLED.value) \ + .filter(InvoiceReference.invoice_id.in_(consolidated_invoice_references)) \ + .distinct(InvoiceReference.invoice_number) \ + .all() + return original_invoice_references + class InvoiceReferenceSchema(BaseSchema): # pylint: disable=too-many-ancestors """Main schema used to serialize the invoice reference.""" diff --git a/pay-api/src/pay_api/models/non_sufficient_funds.py b/pay-api/src/pay_api/models/non_sufficient_funds.py index 21dc9bd13..fde08b761 100644 --- a/pay-api/src/pay_api/models/non_sufficient_funds.py +++ b/pay-api/src/pay_api/models/non_sufficient_funds.py @@ -21,7 +21,7 @@ from .db import db -class NonSufficientFundsModel(BaseModel): # pylint: disable=too-many-instance-attributes +class NonSufficientFunds(BaseModel): # pylint: disable=too-many-instance-attributes """This class manages all of the base data about Non-Sufficient Funds.""" __tablename__ = 'non_sufficient_funds' @@ -51,6 +51,11 @@ class NonSufficientFundsModel(BaseModel): # pylint: disable=too-many-instance-a invoice_id = db.Column(db.Integer, ForeignKey('invoices.id'), nullable=False) invoice_number = db.Column(db.String(50), nullable=False, index=True, comment='CFS Invoice number') + @classmethod + def find_by_invoice_id(cls, invoice_id: int): + """Find a record by invoice id.""" + return cls.query.filter_by(invoice_id=invoice_id).all() + @define class NonSufficientFundsSchema: # pylint: disable=too-few-public-methods @@ -63,7 +68,7 @@ class NonSufficientFundsSchema: # pylint: disable=too-few-public-methods description: str @classmethod - def from_row(cls, row: NonSufficientFundsModel): + def from_row(cls, row: NonSufficientFunds): """From row is used so we don't tightly couple to our database class. https://www.attrs.org/en/stable/init.html diff --git a/pay-api/src/pay_api/models/partner_disbursements.py b/pay-api/src/pay_api/models/partner_disbursements.py index 638292638..759a88462 100644 --- a/pay-api/src/pay_api/models/partner_disbursements.py +++ b/pay-api/src/pay_api/models/partner_disbursements.py @@ -38,27 +38,23 @@ class PartnerDisbursements(BaseModel): # pylint: disable=too-many-instance-attr 'id', 'amount', 'created_on', - 'disbursement_type', 'feedback_on', 'is_reversal', 'partner_code', 'processed_on', - 'source_gl', 'status_code', 'target_id', - 'target_gl' + 'target_type' ] } id = db.Column(db.Integer, primary_key=True, autoincrement=True) amount = db.Column(db.Numeric, nullable=False) created_on = db.Column('created_on', db.DateTime, nullable=False, default=datetime.now) - disbursement_type = db.Column('disbursement_type', db.String(50), nullable=False) feedback_on = db.Column('feedback_on', db.DateTime, nullable=True) partner_code = db.Column('partner_code', db.String(50), nullable=False) processed_on = db.Column('processed_on', db.DateTime, nullable=True) is_reversal = db.Column('is_reversal', db.Boolean(), nullable=False, default=False) - source_gl = db.Column('source_gl', db.String(50), nullable=False) status_code = db.Column('status_code', db.String(25), nullable=False) target_id = db.Column(db.Integer, nullable=True) - target_gl = db.Column('target_gl', db.String(50), nullable=False) + target_type = db.Column(db.String(50), nullable=True) diff --git a/pay-api/src/pay_api/models/payment.py b/pay-api/src/pay_api/models/payment.py index fc996bc5b..1ece83a25 100644 --- a/pay-api/src/pay_api/models/payment.py +++ b/pay-api/src/pay_api/models/payment.py @@ -19,7 +19,8 @@ import pytz from flask import current_app from marshmallow import fields -from sqlalchemy import Boolean, ForeignKey, String, and_, cast, func, not_, or_, text +from sqlalchemy import Boolean, ForeignKey, String, and_, cast, func, or_, select, text +from sqlalchemy.dialects.postgresql import ARRAY, TEXT from sqlalchemy.orm import contains_eager, lazyload, load_only, relationship from sqlalchemy.sql.expression import literal @@ -227,6 +228,7 @@ def search_purchase_history(cls, # noqa:E501; pylint:disable=too-many-arguments Invoice.filing_id, Invoice.bcol_account, Invoice.disbursement_date, + Invoice.disbursement_reversal_date, Invoice.overdue_date ), contains_eager(Invoice.payment_line_items) @@ -261,24 +263,30 @@ def search_purchase_history(cls, # noqa:E501; pylint:disable=too-many-arguments return result, count @classmethod - def get_invoices_for_statements(cls, search_filter: Dict): + def get_invoices_and_payment_accounts_for_statements(cls, search_filter: Dict): """Slimmed down version for statements.""" + auth_account_ids = select(func.unnest(cast(search_filter.get('authAccountIds', []), ARRAY(TEXT)))) query = db.session.query(Invoice) \ .join(PaymentAccount, Invoice.payment_account_id == PaymentAccount.id)\ - .filter(PaymentAccount.auth_account_id.in_(search_filter.get('authAccountIds', []))) - + .filter(PaymentAccount.auth_account_id.in_(auth_account_ids)) # If an account is within these payment methods - limit invoices to these payment methods. # Used for transitioning payment method and an interim statement is created (There could be different payment # methods for the transition day and we don't want it on both statements) - if payment_methods := search_filter.get('matchPaymentMethods', None): + if search_filter.get('matchPaymentMethods', None): query = query.filter( or_( - not_(PaymentAccount.payment_method.in_(payment_methods)), - and_(PaymentAccount.payment_method.in_(payment_methods), - Invoice.payment_method_code == PaymentAccount.payment_method) - )) - - query = cls.filter_date(query, search_filter).with_entities(Invoice.id, PaymentAccount.auth_account_id) + and_(PaymentAccount.payment_method == PaymentMethodEnum.EFT.value, + Invoice.payment_method_code == PaymentAccount.payment_method), + and_(PaymentAccount.payment_method != PaymentMethodEnum.EFT.value, + Invoice.payment_method_code != PaymentMethodEnum.EFT.value + ) + ) + ) + + query = cls.filter_date(query, search_filter).with_entities(Invoice.id, + Invoice.payment_method_code, + PaymentAccount.auth_account_id, + PaymentAccount.id.label('payment_account_id')) return query.all() @classmethod diff --git a/pay-api/src/pay_api/models/payment_account.py b/pay-api/src/pay_api/models/payment_account.py index c487f03b3..e4e1931e8 100644 --- a/pay-api/src/pay_api/models/payment_account.py +++ b/pay-api/src/pay_api/models/payment_account.py @@ -45,6 +45,8 @@ class PaymentAccount(Versioned, BaseModel): # pylint: disable=too-many-instance 'bcol_account', 'bcol_user_id', 'billable', + 'has_nsf_invoices', + 'has_overdue_invoices', 'branch_name', 'credit', 'eft_enable', @@ -71,6 +73,10 @@ class PaymentAccount(Versioned, BaseModel): # pylint: disable=too-many-instance bcol_user_id = db.Column(db.String(50), nullable=True, index=True) bcol_account = db.Column(db.String(50), nullable=True, index=True) + # Either of these below block all payments on accounts. + has_nsf_invoices = db.Column(db.DateTime, nullable=True) + has_overdue_invoices = db.Column(db.DateTime, nullable=True) + # when this is enabled , send out the notifications statement_notification_enabled = db.Column('statement_notification_enabled', Boolean(), default=False) @@ -88,7 +94,7 @@ def __str__(self): return f'{self.name or ""} ({self.auth_account_id})' @classmethod - def find_by_auth_account_id(cls, auth_account_id: str) -> PaymentAccount: + def find_by_auth_account_id(cls, auth_account_id: str) -> PaymentAccount | None: """Return a Account by id.""" return cls.query.filter_by(auth_account_id=str(auth_account_id)).one_or_none() diff --git a/pay-api/src/pay_api/models/payment_line_item.py b/pay-api/src/pay_api/models/payment_line_item.py index 62635f4a5..b39fbf11f 100644 --- a/pay-api/src/pay_api/models/payment_line_item.py +++ b/pay-api/src/pay_api/models/payment_line_item.py @@ -16,7 +16,8 @@ from decimal import Decimal from attrs import define from marshmallow import fields -from sqlalchemy import ForeignKey +from sqlalchemy import ForeignKey, cast, func, select +from sqlalchemy.dialects.postgresql import ARRAY, INTEGER from sqlalchemy.orm import relationship from .base_model import BaseModel @@ -82,8 +83,10 @@ class PaymentLineItem(BaseModel): # pylint: disable=too-many-instance-attribute @classmethod def find_by_invoice_ids(cls, invoice_ids: list): """Return list of line items by list of invoice ids.""" - return db.session.query(PaymentLineItem).filter(PaymentLineItem.invoice_id.in_(invoice_ids)).order_by( - PaymentLineItem.invoice_id.desc()).all() + invoice_ids = select(func.unnest(cast(invoice_ids, ARRAY(INTEGER)))) + return db.session.query(PaymentLineItem).filter(PaymentLineItem.invoice_id.in_( + invoice_ids)) \ + .order_by(PaymentLineItem.invoice_id.desc()).all() class PaymentLineItemSchema(ma.SQLAlchemyAutoSchema): # pylint: disable=too-many-ancestors diff --git a/pay-api/src/pay_api/models/payment_transaction.py b/pay-api/src/pay_api/models/payment_transaction.py index 0e38d3966..09faee7af 100644 --- a/pay-api/src/pay_api/models/payment_transaction.py +++ b/pay-api/src/pay_api/models/payment_transaction.py @@ -13,7 +13,7 @@ # limitations under the License. """Model to handle all operations related to Payment Transaction.""" import uuid -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import pytz from marshmallow import fields @@ -64,7 +64,7 @@ class PaymentTransaction(BaseModel): # pylint: disable=too-few-public-methods, pay_response_url = db.Column(db.String(2000), nullable=True) pay_system_reason_code = db.Column(db.String(2000), nullable=True) - transaction_start_time = db.Column(db.DateTime, default=datetime.now, nullable=False) + transaction_start_time = db.Column(db.DateTime, default=lambda: datetime.now(tz=timezone.utc), nullable=False) transaction_end_time = db.Column(db.DateTime, nullable=True) @classmethod @@ -139,7 +139,7 @@ def find_stale_records(cls, days: int = 0, hours: int = 0, minutes: int = 0): # pylint: disable=import-outside-toplevel, cyclic-import from .payment import Payment - oldest_transaction_time = datetime.now() - (timedelta(days=days, hours=hours, minutes=minutes)) + oldest_transaction_time = datetime.now(tz=timezone.utc) - (timedelta(days=days, hours=hours, minutes=minutes)) completed_status = [TransactionStatus.COMPLETED.value, TransactionStatus.CANCELLED.value, TransactionStatus.FAILED.value] return db.session.query(PaymentTransaction)\ diff --git a/pay-api/src/pay_api/models/refunds_partial.py b/pay-api/src/pay_api/models/refunds_partial.py index 8147ba687..0afd493b2 100644 --- a/pay-api/src/pay_api/models/refunds_partial.py +++ b/pay-api/src/pay_api/models/refunds_partial.py @@ -44,8 +44,6 @@ class RefundsPartial(Audit, Versioned, BaseModel): # pylint: disable=too-many-i 'created_by', 'created_on', 'created_name', - 'disbursement_status_code', - 'disbursement_date', 'payment_line_item_id', 'refund_amount', 'refund_type', @@ -59,8 +57,6 @@ class RefundsPartial(Audit, Versioned, BaseModel): # pylint: disable=too-many-i payment_line_item_id = db.Column(db.Integer, ForeignKey('payment_line_items.id'), nullable=False, index=True) refund_amount = db.Column(db.Numeric(19, 2), nullable=False) refund_type = db.Column(db.String(50), nullable=True) - disbursement_status_code = db.Column(db.String(20), ForeignKey('disbursement_status_codes.code'), nullable=True) - disbursement_date = db.Column(db.DateTime, nullable=True) @define diff --git a/pay-api/src/pay_api/models/statement.py b/pay-api/src/pay_api/models/statement.py index 0227a044b..0c775df0b 100644 --- a/pay-api/src/pay_api/models/statement.py +++ b/pay-api/src/pay_api/models/statement.py @@ -12,12 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. """Model to handle statements data.""" +from __future__ import annotations +from typing import List +from attr import define import pytz from marshmallow import fields from sqlalchemy import ForeignKey, Integer, cast from pay_api.utils.constants import LEGISLATIVE_TIMEZONE +from pay_api.utils.converter import Converter from .base_model import BaseModel from .db import db, ma @@ -47,7 +51,9 @@ class Statement(BaseModel): 'is_interim_statement', 'notification_date', 'notification_status_code', + 'overdue_notification_date', 'payment_account_id', + 'payment_methods', 'statement_settings_id', 'to_date' ] @@ -61,10 +67,11 @@ class Statement(BaseModel): from_date = db.Column(db.Date, default=None, nullable=False) to_date = db.Column(db.Date, default=None, nullable=True) is_interim_statement = db.Column('is_interim_statement', db.Boolean(), nullable=False, default=False) - + overdue_notification_date = db.Column(db.Date, default=None, nullable=True) created_on = db.Column(db.Date, default=None, nullable=False) notification_status_code = db.Column(db.String(20), ForeignKey('notification_status_codes.code'), nullable=True) notification_date = db.Column(db.Date, default=None, nullable=True) + payment_methods = db.Column(db.String(100), nullable=True) @classmethod def find_all_statements_by_notification_status(cls, statuses): @@ -73,14 +80,15 @@ def find_all_statements_by_notification_status(cls, statuses): .filter(Statement.notification_status_code.in_(statuses)).all() @classmethod - def find_all_payments_and_invoices_for_statement(cls, statement_id: str): + def find_all_payments_and_invoices_for_statement(cls, statement_id: str) -> List[Invoice]: """Find all payment and invoices specific to a statement.""" # Import from here as the statement invoice already imports statement and causes circular import. from .statement_invoices import StatementInvoices # pylint: disable=import-outside-toplevel query = db.session.query(Invoice) \ .join(StatementInvoices, StatementInvoices.invoice_id == Invoice.id) \ - .filter(StatementInvoices.statement_id == cast(statement_id, Integer)) + .filter(StatementInvoices.statement_id == cast(statement_id, Integer)) \ + .order_by(Invoice.id.asc()) return query.all() @@ -97,5 +105,40 @@ class Meta: # pylint: disable=too-few-public-methods from_date = fields.Date(tzinfo=pytz.timezone(LEGISLATIVE_TIMEZONE)) to_date = fields.Date(tzinfo=pytz.timezone(LEGISLATIVE_TIMEZONE)) is_overdue = fields.Boolean() - payment_methods = fields.List(fields.String()) + payment_methods = fields.Method(serialize='payment_methods_to_list') amount_owing = fields.Float(load_default=0) + + def payment_methods_to_list(self, target): + """Convert comma separated string to list.""" + return target.payment_methods.split(',') if target.payment_methods else [] + + +@define +class StatementDTO: # pylint: disable=too-few-public-methods, too-many-instance-attributes + """Schema used for Statements to be converted into dtos.""" + + id: int + is_interim_statement: bool + frequency: str + from_date: str + payment_methods: str + to_date: str + + @classmethod + def from_row(cls, row): + """From row is used so we don't tightly couple to our database class. + + https://www.attrs.org/en/stable/init.html + + """ + return cls(id=row.id, frequency=row.frequency, from_date=row.from_date, + is_interim_statement=row.is_interim_statement, payment_methods=row.payment_methods, + to_date=row.to_date) + + @classmethod + def dao_to_dict(cls, statement_daos: List[Statement]) -> dict[StatementDTO]: + """Convert from DAO to DTO dict.""" + statements_dto = [StatementDTO.from_row(statement) for statement in statement_daos] + statements_dict = Converter().unstructure(statements_dto) + statements_dict = [Converter().remove_nones(statement_dict) for statement_dict in statements_dict] + return statements_dict diff --git a/pay-api/src/pay_api/models/statement_invoices.py b/pay-api/src/pay_api/models/statement_invoices.py index 92060cb53..d0250bad5 100644 --- a/pay-api/src/pay_api/models/statement_invoices.py +++ b/pay-api/src/pay_api/models/statement_invoices.py @@ -47,7 +47,7 @@ class StatementInvoices(BaseModel): id = db.Column(db.Integer, primary_key=True, autoincrement=True) statement_id = db.Column(db.Integer, ForeignKey('statements.id'), nullable=False, index=True) - invoice_id = db.Column(db.Integer, ForeignKey('invoices.id'), nullable=False) + invoice_id = db.Column(db.Integer, ForeignKey('invoices.id'), nullable=False, index=True) @classmethod def find_all_invoices_for_statement(cls, statement_identifier: str): diff --git a/pay-api/src/pay_api/models/statement_settings.py b/pay-api/src/pay_api/models/statement_settings.py index 325b31b80..ea016c0d5 100644 --- a/pay-api/src/pay_api/models/statement_settings.py +++ b/pay-api/src/pay_api/models/statement_settings.py @@ -12,13 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. """Model to handle statements data.""" -from datetime import date, datetime +from datetime import datetime, timezone from sqlalchemy import ForeignKey -from pay_api.utils.enums import StatementFrequency -from pay_api.utils.util import get_local_time - from .base_model import BaseModel from .db import db, ma from .payment_account import PaymentAccount @@ -52,16 +49,15 @@ class StatementSettings(BaseModel): frequency = db.Column(db.String(50), nullable=True, index=True) payment_account_id = db.Column(db.Integer, ForeignKey('payment_accounts.id'), nullable=True, index=True) - from_date = db.Column(db.Date, default=date.today(), nullable=False) + from_date = db.Column(db.Date, default=lambda: datetime.now(tz=timezone.utc).date(), nullable=False) to_date = db.Column(db.Date, default=None, nullable=True) @classmethod def find_active_settings(cls, auth_account_id: str, valid_date: datetime): """Return active statement setting for the account.""" - valid_date = get_local_time(valid_date) query = cls.query.join(PaymentAccount).filter(PaymentAccount.auth_account_id == auth_account_id) # need this to strip of the time information from the date - todays_datetime = valid_date.today().date() + todays_datetime = valid_date.date() query = query.filter(StatementSettings.from_date <= todays_datetime). \ filter((StatementSettings.to_date.is_(None)) | (StatementSettings.to_date >= todays_datetime)) @@ -74,18 +70,6 @@ def find_latest_settings(cls, auth_account_id: str): query = query.filter((StatementSettings.to_date.is_(None))) return query.one_or_none() - @classmethod - def find_accounts_settings_by_frequency(cls, valid_date: datetime, frequency: StatementFrequency): - """Return active statement setting for the account.""" - valid_date = get_local_time(valid_date).date() - query = db.session.query(StatementSettings, PaymentAccount).join(PaymentAccount) - - query = query.filter(StatementSettings.from_date <= valid_date). \ - filter((StatementSettings.to_date.is_(None)) | (StatementSettings.to_date >= valid_date)). \ - filter(StatementSettings.frequency == frequency.value) - - return query.all() - class StatementSettingsSchema(ma.SQLAlchemyAutoSchema): # pylint: disable=too-many-ancestors """Main schema used to serialize the Statements settings.""" diff --git a/pay-api/src/pay_api/resources/ops.py b/pay-api/src/pay_api/resources/ops.py index c399771a1..9ac0408af 100755 --- a/pay-api/src/pay_api/resources/ops.py +++ b/pay-api/src/pay_api/resources/ops.py @@ -38,5 +38,8 @@ def get_ops_healthz(): @bp.route('readyz') def get_ops_readyz(): """Return a JSON object that identifies if the service is setupAnd ready to work.""" - # TODO: add a poll to the DB when called + try: + db.session.execute(SQL) + except exc.SQLAlchemyError: + return {'message': 'api is down'}, 500 return {'message': 'api is ready'}, 200 diff --git a/pay-api/src/pay_api/resources/v1/account.py b/pay-api/src/pay_api/resources/v1/account.py index eb1924ae5..5f969babf 100644 --- a/pay-api/src/pay_api/resources/v1/account.py +++ b/pay-api/src/pay_api/resources/v1/account.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Resource for Payment account.""" -from datetime import datetime +from datetime import datetime, timezone from http import HTTPStatus from flask import Blueprint, Response, abort, current_app, jsonify, request @@ -266,7 +266,7 @@ def post_account_purchase_report(account_number: str): if not valid_format: return error_to_response(Error.INVALID_REQUEST, invalid_params=schema_utils.serialize(errors)) - report_name = f"bcregistry-transactions-{datetime.now().strftime('%m-%d-%Y')}" + report_name = f"bcregistry-transactions-{datetime.now(tz=timezone.utc).strftime('%m-%d-%Y')}" if response_content_type == ContentType.PDF.value: report_name = f'{report_name}.pdf' diff --git a/pay-api/src/pay_api/resources/v1/eft_short_names.py b/pay-api/src/pay_api/resources/v1/eft_short_names.py index 5a157e1a4..2578190dc 100644 --- a/pay-api/src/pay_api/resources/v1/eft_short_names.py +++ b/pay-api/src/pay_api/resources/v1/eft_short_names.py @@ -23,8 +23,8 @@ from pay_api.services.eft_short_names import EFTShortnames as EFTShortnameService from pay_api.services.eft_short_name_summaries import EFTShortnameSummaries as EFTShortnameSummariesService from pay_api.services.eft_short_names import EFTShortnamesSearch -from pay_api.services.eft_transactions import EFTTransactions as EFTTransactionService -from pay_api.services.eft_transactions import EFTTransactionSearch +from pay_api.services.eft_short_name_historical import EFTShortnameHistorical as EFTShortnameHistoryService +from pay_api.services.eft_short_name_historical import EFTShortnameHistorySearch from pay_api.utils.auth import jwt as _jwt from pay_api.utils.endpoints_enums import EndpointEnum from pay_api.utils.enums import Role @@ -52,10 +52,13 @@ def get_eft_shortnames(): account_id = request.args.get('accountId', None) account_name = request.args.get('accountName', None) account_branch = request.args.get('accountBranch', None) + account_id_list = request.args.get('accountIdList', None) + account_id_list = account_id_list.split(',') if account_id_list else None response, status = EFTShortnameService.search(EFTShortnamesSearch( id=short_name_id, account_id=account_id, + account_id_list=account_id_list, account_name=account_name, account_branch=account_branch, amount_owing=string_to_decimal(amount_owing), @@ -115,21 +118,21 @@ def get_eft_shortname(short_name_id: int): return jsonify(response), status -@bp.route('//transactions', methods=['GET', 'OPTIONS']) +@bp.route('//history', methods=['GET', 'OPTIONS']) @cross_origin(origins='*', methods=['GET']) @_jwt.requires_auth @_jwt.has_one_of_roles([Role.SYSTEM.value, Role.MANAGE_EFT.value]) -def get_eft_shortname_transactions(short_name_id: int): - """Get EFT short name transactions.""" - current_app.logger.info('get_eft_shortname_transactions') + response, status = (EFTShortnameHistoryService.search(short_name_id, + EFTShortnameHistorySearch(page=page, limit=limit)), + HTTPStatus.OK) + current_app.logger.debug('>get_eft_shortname_history') return jsonify(response), status diff --git a/pay-api/src/pay_api/resources/v1/fee.py b/pay-api/src/pay_api/resources/v1/fee.py index e27671140..1da5c8df7 100644 --- a/pay-api/src/pay_api/resources/v1/fee.py +++ b/pay-api/src/pay_api/resources/v1/fee.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Resource for Fee Calculation endpoints.""" -from datetime import datetime +from datetime import datetime, timezone from http import HTTPStatus from flask import Blueprint, jsonify, request @@ -35,7 +35,7 @@ @_jwt.has_one_of_roles([Role.VIEWER.value, Role.EDITOR.value, Role.STAFF.value]) def get_fee_by_corp_and_filing_type(corp_type, filing_type_code): """Calculate the fee for the filing using the corp type/filing type and return fee.""" - date = request.args.get('date', datetime.today().strftime(DT_SHORT_FORMAT)) + date = request.args.get('date', datetime.now(tz=timezone.utc).strftime(DT_SHORT_FORMAT)) is_priority = convert_to_bool(request.args.get('priority', 'False')) is_future_effective = convert_to_bool(request.args.get('futureEffective', 'False')) jurisdiction = request.args.get('jurisdiction', DEFAULT_JURISDICTION) diff --git a/pay-api/src/pay_api/resources/v1/payment.py b/pay-api/src/pay_api/resources/v1/payment.py index 94d8414a3..b5c81da45 100644 --- a/pay-api/src/pay_api/resources/v1/payment.py +++ b/pay-api/src/pay_api/resources/v1/payment.py @@ -66,22 +66,25 @@ def post_account_payment(account_id: str): return error_to_response(Error.INVALID_REQUEST, invalid_params=schema_utils.serialize(errors)) if credit_request.get('paymentMethod') in \ - (PaymentMethod.EFT.value, PaymentMethod.WIRE.value, PaymentMethod.DRAWDOWN.value): + (PaymentMethod.EFT.value, PaymentMethod.DRAWDOWN.value): response, status = PaymentService.create_payment_receipt( auth_account_id=account_id, credit_request=credit_request ).asdict(), HTTPStatus.CREATED else: is_retry_payment: bool = request.args.get('retryFailedPayment', 'false').lower() == 'true' - pay_outstanding_balance: bool = False + pay_outstanding_balance = False + all_invoice_statuses = False if flags.is_on('enable-eft-payment-method', default=False): pay_outstanding_balance = request.args.get('payOutstandingBalance', 'false').lower() == 'true' + all_invoice_statuses = request.args.get('allInvoiceStatuses', 'false').lower() == 'true' response, status = PaymentService.create_account_payment( auth_account_id=account_id, is_retry_payment=is_retry_payment, - pay_outstanding_balance=pay_outstanding_balance + pay_outstanding_balance=pay_outstanding_balance, + all_invoice_statuses=all_invoice_statuses, ).asdict(), HTTPStatus.CREATED current_app.logger.debug('>post_account_payment') diff --git a/pay-api/src/pay_api/services/__init__.py b/pay-api/src/pay_api/services/__init__.py index b564310f8..96d9aec93 100755 --- a/pay-api/src/pay_api/services/__init__.py +++ b/pay-api/src/pay_api/services/__init__.py @@ -16,8 +16,10 @@ from .cfs_service import CFSService from .distribution_code import DistributionCode from .eft_service import EftService -from .eft_short_names import EFTShortnames as EFTShortNamesService +from .eft_short_name_historical import EFTShortnameHistorical as EFTShortNameHistoricalService +from .eft_short_name_historical import EFTShortnameHistory, EFTShortnameHistorySearch from .eft_short_name_summaries import EFTShortnameSummaries as EFTShortNameSummaryService +from .eft_short_names import EFTShortnames as EFTShortNamesService from .fee_schedule import FeeSchedule from .hashing import HashingService from .internal_pay_service import InternalPayService @@ -27,8 +29,8 @@ from .payment_service import PaymentService from .payment_transaction import PaymentTransaction as TransactionService from .receipt import Receipt as ReceiptService -from .report_service import ReportService from .refund import RefundService +from .report_service import ReportService from .statement import Statement from .statement_recipients import StatementRecipients from .statement_settings import StatementSettings diff --git a/pay-api/src/pay_api/services/auth.py b/pay-api/src/pay_api/services/auth.py index 015d3a49c..33b89199a 100644 --- a/pay-api/src/pay_api/services/auth.py +++ b/pay-api/src/pay_api/services/auth.py @@ -106,3 +106,13 @@ def check_auth(business_identifier: str, account_id: str = None, corp_type_code: auth_response = {'account': {'id': user.user_name, 'paymentInfo': {'methodOfPayment': PaymentMethod.DIRECT_PAY.value}}} return auth_response + + +@user_context +def get_account_admin_users(auth_account_id, **kwargs): + """Retrieve account admin users.""" + return RestService.get( + current_app.config.get('AUTH_API_ENDPOINT') + + f'orgs/{auth_account_id}/members?status=ACTIVE&roles=ADMIN', + kwargs['user'].bearer_token, AuthHeaderType.BEARER, + ContentType.JSON).json() diff --git a/pay-api/src/pay_api/services/base_payment_system.py b/pay-api/src/pay_api/services/base_payment_system.py index 42756fdc4..997ef6544 100644 --- a/pay-api/src/pay_api/services/base_payment_system.py +++ b/pay-api/src/pay_api/services/base_payment_system.py @@ -15,12 +15,12 @@ import functools from abc import ABC, abstractmethod -from datetime import datetime +from datetime import datetime, timezone from typing import Any, Dict, List from flask import current_app -from sentry_sdk import capture_message from sbc_common_components.utils.enums import QueueMessageTypes +from sentry_sdk import capture_message from pay_api.exceptions import BusinessException from pay_api.models import CfsAccount as CfsAccountModel @@ -39,8 +39,7 @@ from pay_api.services.payment import Payment from pay_api.services.payment_account import PaymentAccount from pay_api.utils.enums import ( - CfsAccountStatus, CorpType, InvoiceReferenceStatus, InvoiceStatus, PaymentMethod, PaymentStatus, QueueSources, - TransactionStatus) + CorpType, InvoiceReferenceStatus, InvoiceStatus, PaymentMethod, PaymentStatus, QueueSources, TransactionStatus) from pay_api.utils.errors import Error from pay_api.utils.user_context import UserContext from pay_api.utils.util import get_local_formatted_date_time, get_topic_for_corp_type @@ -145,12 +144,11 @@ def apply_credit(self, invoice: Invoice) -> None: # pylint:disable=unused-argum def ensure_no_payment_blockers(self, payment_account: PaymentAccount) -> None: # pylint: disable=unused-argument """Ensure no payment blockers are present.""" - cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account.id, PaymentMethod.PAD.value) - if cfs_account and cfs_account.status == CfsAccountStatus.FREEZE.value: + if payment_account.has_nsf_invoices: # Note NSF (Account Unlocking) is paid using DIRECT_PAY - CC flow, not PAD. current_app.logger.warning(f'Account {payment_account.id} is frozen, rejecting invoice creation') raise BusinessException(Error.PAD_CURRENTLY_NSF) - if Invoice.has_overdue_invoices(payment_account.id): + if payment_account.has_overdue_invoices: raise BusinessException(Error.EFT_INVOICES_OVERDUE) @staticmethod @@ -169,7 +167,8 @@ def _release_payment(invoice: Invoice): source=QueueSources.PAY_API.value, message_type=QueueMessageTypes.PAYMENT.value, payload=payload, - topic=get_topic_for_corp_type(invoice.corp_type_code) + topic=get_topic_for_corp_type(invoice.corp_type_code), + corp_type=invoice.corp_type_code ) ) except Exception as e: # NOQA pylint: disable=broad-except @@ -233,7 +232,7 @@ def _publish_refund_to_mailer(invoice: InvoiceModel): 'transactionDateTime': get_local_formatted_date_time(transaction_date_time), 'transactionAmount': receipt.receipt_amount, 'transactionId': invoice_ref.invoice_number, - 'refundDate': get_local_formatted_date_time(datetime.now(), '%Y%m%d'), + 'refundDate': get_local_formatted_date_time(datetime.now(tz=timezone.utc), '%Y%m%d'), 'filingDescription': filing_description } if invoice.payment_method_code == PaymentMethod.DRAWDOWN.value: @@ -265,7 +264,7 @@ def complete_payment(self, invoice, invoice_reference): payment_account_id=invoice.payment_account_id) invoice.invoice_status_code = InvoiceStatus.PAID.value invoice.paid = invoice.total - current_time = datetime.now() + current_time = datetime.now(tz=timezone.utc) invoice.payment_date = current_time invoice_reference.status_code = InvoiceReferenceStatus.COMPLETED.value # Create receipt. diff --git a/pay-api/src/pay_api/services/bcol_service.py b/pay-api/src/pay_api/services/bcol_service.py index c099afead..fd76577a4 100644 --- a/pay-api/src/pay_api/services/bcol_service.py +++ b/pay-api/src/pay_api/services/bcol_service.py @@ -13,7 +13,7 @@ # limitations under the License. """Service to manage PayBC interaction.""" -from datetime import datetime +from datetime import datetime, timezone from typing import Dict, List from flask import current_app @@ -128,7 +128,7 @@ def get_receipt(self, payment_account: PaymentAccount, pay_response_url: str, in """Get receipt from bcol for the receipt number or get receipt against invoice number.""" current_app.logger.debug('Creating Adjustment for Invoice: %s', inv_number) access_token: str = CFSService.get_token().json().get('access_token') cfs_base: str = current_app.config.get('CFS_BASE_URL') @@ -502,7 +511,7 @@ def adjust_invoice(cls, cfs_account: CfsAccountModel, inv_number: str, amount: f adjustment = { 'comment': 'Invoice cancellation', - 'lines': [ + 'lines': adjustment_lines or [ { 'line_number': '1', 'adjustment_amount': str(amount), @@ -597,7 +606,7 @@ def create_cms(cls, line_items: List[PaymentLineItemModel], cfs_account: CfsAcco 'transaction_date': curr_time, 'gl_date': curr_time, 'comments': '', - 'lines': cls._build_lines(line_items, negate=True) + 'lines': cls.build_lines(line_items, negate=True) } cms_response = CFSService.post(cms_url, access_token, AuthHeaderType.BEARER, ContentType.JSON, cms_payload) diff --git a/pay-api/src/pay_api/services/direct_pay_service.py b/pay-api/src/pay_api/services/direct_pay_service.py index d27031caf..b2bd9803d 100644 --- a/pay-api/src/pay_api/services/direct_pay_service.py +++ b/pay-api/src/pay_api/services/direct_pay_service.py @@ -14,12 +14,14 @@ """Service to manage Direct Pay PAYBC Payments.""" import base64 from decimal import Decimal +import json from typing import List, Optional from urllib.parse import unquote_plus, urlencode from attrs import define from dateutil import parser from flask import current_app +from requests import HTTPError from pay_api.models import Invoice as InvoiceModel from pay_api.models import InvoiceReference as InvoiceReferenceModel @@ -87,6 +89,7 @@ class OrderStatus(): revenue: List[RevenueLine] postedrefundamount: Optional[Decimal] refundedamount: Optional[Decimal] + paymentstatus: Optional[str] class DirectPayService(PaymentSystemService, OAuthService): @@ -202,13 +205,30 @@ def process_cfs_refund(self, invoice: InvoiceModel, refund_url = current_app.config.get('PAYBC_DIRECT_PAY_CC_REFUND_BASE_URL') + '/paybc-service/api/refund' access_token: str = self._get_refund_token().json().get('access_token') data = self.build_automated_refund_payload(invoice, refund_partial) - refund_response = self.post(refund_url, access_token, AuthHeaderType.BEARER, - ContentType.JSON, data, auth_header_name='Bearer-Token').json() - # Check if approved is 1=Success - if refund_response.get('approved') != 1: - message = 'Refund error: ' + refund_response.get('message') - current_app.logger.error(message) - raise BusinessException(Error.DIRECT_PAY_INVALID_RESPONSE) + + try: + refund_response = self.post(refund_url, access_token, AuthHeaderType.BEARER, + ContentType.JSON, data, auth_header_name='Bearer-Token').json() + # Check if approved is 1=Success + if refund_response.get('approved') != 1: + message = 'Refund error: ' + refund_response.get('message') + current_app.logger.error(message) + raise BusinessException(Error.DIRECT_PAY_INVALID_RESPONSE) + + except HTTPError as e: + current_app.logger.error(f'PayBC Refund request failed: {str(e)}') + error_detail = None + error = Error.DIRECT_PAY_INVALID_RESPONSE + if e.response is not None: + try: + error_response = json.loads(e.response.text) + error_detail = error_response.get('errors') + except json.JSONDecodeError: + error_detail = 'Error decoding JSON response from PayBC.' + + error.detail = error_detail + raise BusinessException(error) from e + current_app.logger.debug('>process_cfs_refund') def get_receipt(self, payment_account: PaymentAccount, pay_response_url: str, invoice_reference: InvoiceReference): @@ -340,16 +360,17 @@ def _build_refund_revenue_lines(refund_partial: List[RefundPartialLine]): return refund_lines, total @classmethod - def _query_order_status(cls, invoice: InvoiceModel) -> OrderStatus: + def query_order_status(cls, invoice: InvoiceModel, + inv_status: InvoiceReferenceStatus = InvoiceReferenceStatus.COMPLETED.value) -> OrderStatus: """Request invoice order status from PAYBC.""" access_token: str = DirectPayService().get_token().json().get('access_token') paybc_ref_number: str = current_app.config.get('PAYBC_DIRECT_PAY_REF_NUMBER') paybc_svc_base_url = current_app.config.get('PAYBC_DIRECT_PAY_BASE_URL') - completed_reference = list( - filter(lambda reference: (reference.status_code == InvoiceReferenceStatus.COMPLETED.value), + inv_reference = list( + filter(lambda reference: (reference.status_code == inv_status), invoice.references))[0] payment_url: str = \ - f'{paybc_svc_base_url}/paybc/payment/{paybc_ref_number}/{completed_reference.invoice_number}' + f'{paybc_svc_base_url}/paybc/payment/{paybc_ref_number}/{inv_reference.invoice_number}' payment_response = cls.get(payment_url, access_token, AuthHeaderType.BEARER, ContentType.JSON).json() return Converter().structure(payment_response, OrderStatus) @@ -369,7 +390,7 @@ def build_automated_refund_payload(cls, invoice: InvoiceModel, refund_partial: L return refund_payload refund_lines, total_refund = DirectPayService._build_refund_revenue_lines(refund_partial) - paybc_invoice = DirectPayService._query_order_status(invoice) + paybc_invoice = DirectPayService.query_order_status(invoice) refund_payload.update({ 'refundRevenue': DirectPayService._build_refund_revenue(paybc_invoice, refund_lines), 'txnAmount': total_refund diff --git a/pay-api/src/pay_api/services/distribution_code.py b/pay-api/src/pay_api/services/distribution_code.py index c69902789..bdcd23d12 100644 --- a/pay-api/src/pay_api/services/distribution_code.py +++ b/pay-api/src/pay_api/services/distribution_code.py @@ -14,7 +14,7 @@ """Service to manage Fee Calculation.""" from __future__ import annotations -from datetime import date +from datetime import date, datetime, timezone from typing import Dict from dateutil import parser @@ -366,7 +366,7 @@ def save_or_update(distribution_details: Dict, dist_id: int = None): if distribution_details.get('startDate', None): dist_code_svc.start_date = parser.parse(distribution_details.get('startDate')) else: - dist_code_svc.start_date = date.today() + dist_code_svc.start_date = datetime.now(tz=timezone.utc).date() _has_code_changes: bool = dist_code_svc.client != distribution_details.get('client', None) \ or dist_code_svc.responsibility_centre != distribution_details.get('responsibilityCentre', None) \ diff --git a/pay-api/src/pay_api/services/eft_service.py b/pay-api/src/pay_api/services/eft_service.py index 85128e096..c639f90cd 100644 --- a/pay-api/src/pay_api/services/eft_service.py +++ b/pay-api/src/pay_api/services/eft_service.py @@ -12,24 +12,36 @@ # See the License for the specific language governing permissions and # limitations under the License. """Service to manage CFS EFT Payments.""" -import os +from decimal import Decimal from datetime import datetime from typing import Any, Dict, List from flask import current_app -from jinja2 import Environment, FileSystemLoader + +from pay_api.models import CorpType as CorpTypeModel +from pay_api.exceptions import BusinessException from pay_api.models import CfsAccount as CfsAccountModel +from pay_api.models import EFTCreditInvoiceLink as EFTCreditInvoiceLinkModel +from pay_api.models import EFTCredit as EFTCreditModel +from pay_api.models import EFTShortnamesHistorical as EFTHistoryModel from pay_api.models import EFTRefund as EFTRefundModel from pay_api.models import Invoice as InvoiceModel from pay_api.models import InvoiceReference as InvoiceReferenceModel +from pay_api.models import PartnerDisbursements as PartnerDisbursementsModel from pay_api.models import Payment as PaymentModel from pay_api.models import PaymentAccount as PaymentAccountModel from pay_api.models import Receipt as ReceiptModel +from pay_api.models import RefundPartialLine from pay_api.models.eft_refund_email_list import EFTRefundEmailList -from pay_api.services.email_service import send_email +from pay_api.services.eft_short_names import EFTShortnames +from pay_api.services.eft_short_name_historical import EFTShortnameHistorical as EFTHistoryService +from pay_api.services.eft_short_name_historical import EFTShortnameHistory as EFTHistory +from pay_api.services.email_service import _render_shortname_details_body, send_email from pay_api.utils.enums import ( - CfsAccountStatus, EFTCreditInvoiceStatus, InvoiceReferenceStatus, PaymentMethod, PaymentStatus, PaymentSystem) + CfsAccountStatus, DisbursementStatus, EFTCreditInvoiceStatus, EJVLinkType, InvoiceReferenceStatus, InvoiceStatus, + PaymentMethod, PaymentStatus, PaymentSystem) +from pay_api.utils.errors import Error from pay_api.utils.user_context import user_context from pay_api.utils.util import get_str_by_path @@ -65,12 +77,27 @@ def create_invoice(self, payment_account: PaymentAccount, line_items: List[Payme **kwargs) -> None: """Do nothing here, we create invoice references on the create CFS_INVOICES job.""" self.ensure_no_payment_blockers(payment_account) + if corp_type := CorpTypeModel.find_by_code(invoice.corp_type_code): + if corp_type.has_partner_disbursements: + PartnerDisbursementsModel( + amount=invoice.total, + disbursement_type=EJVLinkType.INVOICE.value, + is_reversal=False, + is_legacy=False, + partner_code=invoice.corp_type_code, + status_code=DisbursementStatus.WAITING_FOR_JOB.value, + target_id=invoice.id + ).flush() def complete_post_invoice(self, invoice: Invoice, invoice_reference: InvoiceReference) -> None: """Complete any post invoice activities if needed.""" # Publish message to the queue with payment token, so that they can release records on their side. self._release_payment(invoice=invoice) + def get_default_invoice_status(self) -> str: + """Return the default status for invoice when created.""" + return InvoiceStatus.APPROVED.value + def create_payment(self, payment_account: PaymentAccountModel, invoice: InvoiceModel, payment_date: datetime, paid_amount) -> PaymentModel: """Create a payment record for an invoice.""" @@ -85,6 +112,65 @@ def create_payment(self, payment_account: PaymentAccountModel, invoice: InvoiceM receipt_number=invoice.id) return payment + def process_cfs_refund(self, invoice: InvoiceModel, + payment_account: PaymentAccount, + refund_partial: List[RefundPartialLine]): # pylint:disable=unused-argument + """Process refund in CFS.""" + cils = EFTCreditInvoiceLinkModel.find_by_invoice_id(invoice.id) + # 1. Possible to have no CILs and no invoice_reference, nothing to reverse. + if invoice.invoice_status_code == InvoiceStatus.APPROVED.value \ + and InvoiceReferenceModel.find_by_invoice_id_and_status( + invoice.id, InvoiceReferenceStatus.ACTIVE.value) is None and not cils: + return InvoiceStatus.CANCELLED.value + + # 2. No EFT Credit Link - Job needs to reverse invoice in CFS + # (Invoice needs to be reversed, receipt doesn't exist.) + if not cils: + return InvoiceStatus.REFUND_REQUESTED.value + + latest_link = cils[0] + sibling_cils = [cil for cil in cils if cil.link_group_id == latest_link.link_group_id] + latest_eft_credit = EFTCreditModel.find_by_id(latest_link.eft_credit_id) + link_group_id = EFTCreditInvoiceLinkModel.get_next_group_link_seq() + existing_balance = EFTShortnames.get_eft_credit_balance(latest_eft_credit.short_name_id) + + match latest_link.status_code: + case EFTCreditInvoiceStatus.PENDING.value: + # 3. EFT Credit Link - PENDING, CANCEL that link - restore balance to EFT credit existing call + # (Invoice needs to be reversed, receipt doesn't exist.) + for cil in sibling_cils: + EFTShortnames.return_eft_credit(cil, EFTCreditInvoiceStatus.CANCELLED.value) + cil.link_group_id = link_group_id + cil.flush() + case EFTCreditInvoiceStatus.COMPLETED.value: + # 4. EFT Credit Link - COMPLETED + # (Invoice needs to be reversed and receipt needs to be reversed.) + for cil in sibling_cils: + EFTShortnames.return_eft_credit(cil) + EFTCreditInvoiceLinkModel( + eft_credit_id=cil.eft_credit_id, + status_code=EFTCreditInvoiceStatus.PENDING_REFUND.value, + amount=cil.amount, + receipt_number=cil.receipt_number, + invoice_id=invoice.id, + link_group_id=link_group_id).flush() + + current_balance = EFTShortnames.get_eft_credit_balance(latest_eft_credit.short_name_id) + if existing_balance != current_balance: + short_name_history = EFTHistoryModel.find_by_related_group_link_id(latest_link.link_group_id) + EFTHistoryService.create_invoice_refund( + EFTHistory(short_name_id=latest_eft_credit.short_name_id, + amount=invoice.total, + credit_balance=current_balance, + payment_account_id=payment_account.id, + related_group_link_id=link_group_id, + statement_number=short_name_history.statement_number if short_name_history else None, + invoice_id=invoice.id, + is_processing=True, + hidden=False)).flush() + + return InvoiceStatus.REFUND_REQUESTED.value + @staticmethod def create_invoice_reference(invoice: InvoiceModel, invoice_number: str, reference_number: str) -> InvoiceReferenceModel: @@ -103,16 +189,17 @@ def create_invoice_reference(invoice: InvoiceModel, invoice_number: str, @staticmethod def create_receipt(invoice: InvoiceModel, payment: PaymentModel) -> ReceiptModel: """Create a receipt record for an invoice payment.""" - receipt: ReceiptModel = ReceiptModel(receipt_date=payment.payment_date, - receipt_amount=payment.paid_amount, - invoice_id=invoice.id, - receipt_number=payment.receipt_number) + receipt = ReceiptModel(receipt_date=payment.payment_date, + receipt_amount=payment.paid_amount, + invoice_id=invoice.id, + receipt_number=payment.receipt_number) return receipt @classmethod @user_context def create_shortname_refund(cls, request: Dict[str, str], **kwargs) -> Dict[str, str]: """Create refund.""" + # This method isn't for invoices, it's for shortname only. shortname_id = get_str_by_path(request, 'shortNameId') shortname = get_str_by_path(request, 'shortName') amount = get_str_by_path(request, 'refundAmount') @@ -121,18 +208,45 @@ def create_shortname_refund(cls, request: Dict[str, str], **kwargs) -> Dict[str, current_app.logger.debug(f'Starting shortname refund : {shortname_id}') refund = cls._create_refund_model(request, shortname_id, amount, comment) - recipients = EFTRefundEmailList.find_all_emails() + cls._refund_eft_credits(int(shortname_id), amount) + recipients = EFTRefundEmailList.find_all_emails() subject = f'Pending Refund Request for Short Name {shortname}' - html_body = cls._render_email_body(shortname, amount, comment) + html_body = _render_shortname_details_body(shortname, amount, comment, shortname_id) send_email(recipients, subject, html_body, **kwargs) refund.save() + @classmethod + def _refund_eft_credits(cls, shortname_id: int, amount: str): + """Refund the amount to eft_credits table based on short_name_id.""" + refund_amount = Decimal(amount) + eft_credits = EFTShortnames.get_eft_credits(shortname_id) + eft_credit_balance = EFTShortnames.get_eft_credit_balance(shortname_id) + + if refund_amount > eft_credit_balance: + raise BusinessException(Error.INVALID_REFUND) + + for credit in eft_credits: + if refund_amount <= 0: + break + credit_amount = Decimal(credit.remaining_amount) + if credit_amount <= 0: + continue + + deduction = min(refund_amount, credit_amount) + credit.remaining_amount -= deduction + refund_amount -= deduction + + credit.save() + @classmethod def _create_refund_model(cls, request: Dict[str, str], shortname_id: str, amount: str, comment: str) -> EFTRefundModel: """Create and return the EFTRefundModel instance.""" + # AP refund job should pick up this row and send back the amount in the refund via cheque. + # For example if we had $500 on the EFT Shortname credits and we want to refund $300, + # then the AP refund job should send a cheque for $300 to the supplier while leaving $200 on the credits. refund = EFTRefundModel( short_name_id=shortname_id, refund_amount=amount, @@ -142,21 +256,3 @@ def _create_refund_model(cls, request: Dict[str, str], ) refund.status = EFTCreditInvoiceStatus.PENDING_REFUND return refund - - @classmethod - def _render_email_body(cls, shortname: str, amount: str, comment: str) -> str: - """Render the email body using the provided template.""" - current_dir = os.path.dirname(os.path.abspath(__file__)) - project_root_dir = os.path.dirname(current_dir) - templates_dir = os.path.join(project_root_dir, 'templates') - env = Environment(loader=FileSystemLoader(templates_dir), autoescape=True) - template = env.get_template('eft_refund_notification.html') - - url = f"{current_app.config.get('AUTH_WEB_URL')}/account/settings/transactions" - params = { - 'shortname': shortname, - 'refundAmount': amount, - 'comment': comment, - 'url': url - } - return template.render(params) diff --git a/pay-api/src/pay_api/services/eft_short_name_historical.py b/pay-api/src/pay_api/services/eft_short_name_historical.py new file mode 100644 index 000000000..0196a232a --- /dev/null +++ b/pay-api/src/pay_api/services/eft_short_name_historical.py @@ -0,0 +1,216 @@ +# Copyright © 2024 Province of British Columbia +# +# 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. +"""Service to manage EFT Short names historical data.""" +from dataclasses import dataclass +from datetime import datetime, timedelta, timezone +from decimal import Decimal +from typing import Optional + +from sqlalchemy import and_, case, exists, false, func, select +from sqlalchemy.orm import aliased + +from pay_api.models import EFTShortnamesHistorical as EFTShortnamesHistoricalModel +from pay_api.models import PaymentAccount as PaymentAccountModel +from pay_api.models import db +from pay_api.models.eft_short_names_historical import EFTShortnameHistorySchema +from pay_api.utils.enums import EFTHistoricalTypes +from pay_api.utils.user_context import user_context +from pay_api.utils.util import unstructure_schema_items + + +@dataclass +class EFTShortnameHistory: # pylint: disable=too-many-instance-attributes + """Used for creating EFT Short name historical data.""" + + short_name_id: int + amount: Decimal + credit_balance: Decimal + payment_account_id: Optional[int] = None + related_group_link_id: Optional[int] = None + statement_number: Optional[int] = None + hidden: Optional[bool] = False + is_processing: Optional[bool] = False + invoice_id: Optional[int] = None + + +@dataclass +class EFTShortnameHistorySearch: + """Used for searching EFT Short name historical records.""" + + page: Optional[int] = 1 + limit: Optional[int] = 10 + + +class EFTShortnameHistorical: + """Service to manage EFT Short name historical data.""" + + @staticmethod + def create_funds_received(history: EFTShortnameHistory) -> EFTShortnamesHistoricalModel: + """Create EFT Short name funds received historical record.""" + return EFTShortnamesHistoricalModel( + amount=history.amount, + created_by='SYSTEM', + credit_balance=history.credit_balance, + hidden=history.hidden, + is_processing=history.is_processing, + short_name_id=history.short_name_id, + transaction_date=EFTShortnameHistorical.transaction_date_now(), + transaction_type=EFTHistoricalTypes.FUNDS_RECEIVED.value + ) + + @staticmethod + @user_context + def create_statement_paid(history: EFTShortnameHistory, **kwargs) -> EFTShortnamesHistoricalModel: + """Create EFT Short name statement paid historical record.""" + return EFTShortnamesHistoricalModel( + amount=history.amount, + created_by=kwargs['user'].user_name, + credit_balance=history.credit_balance, + hidden=history.hidden, + is_processing=history.is_processing, + payment_account_id=history.payment_account_id, + related_group_link_id=history.related_group_link_id, + short_name_id=history.short_name_id, + statement_number=history.statement_number, + transaction_date=EFTShortnameHistorical.transaction_date_now(), + transaction_type=EFTHistoricalTypes.STATEMENT_PAID.value + ) + + @staticmethod + @user_context + def create_statement_reverse(history: EFTShortnameHistory, **kwargs) -> EFTShortnamesHistoricalModel: + """Create EFT Short name statement reverse historical record.""" + return EFTShortnamesHistoricalModel( + amount=history.amount, + created_by=kwargs['user'].user_name, + credit_balance=history.credit_balance, + hidden=history.hidden, + is_processing=history.is_processing, + payment_account_id=history.payment_account_id, + related_group_link_id=history.related_group_link_id, + short_name_id=history.short_name_id, + statement_number=history.statement_number, + transaction_date=EFTShortnameHistorical.transaction_date_now(), + transaction_type=EFTHistoricalTypes.STATEMENT_REVERSE.value + ) + + @staticmethod + @user_context + def create_invoice_refund(history: EFTShortnameHistory, **kwargs) -> EFTShortnamesHistoricalModel: + """Create EFT Short name invoice refund historical record.""" + return EFTShortnamesHistoricalModel( + amount=history.amount, + created_by=kwargs['user'].user_name, + credit_balance=history.credit_balance, + hidden=history.hidden, + is_processing=history.is_processing, + payment_account_id=history.payment_account_id, + related_group_link_id=history.related_group_link_id, + short_name_id=history.short_name_id, + statement_number=history.statement_number, + invoice_id=history.invoice_id, + transaction_date=EFTShortnameHistorical.transaction_date_now(), + transaction_type=EFTHistoricalTypes.INVOICE_REFUND.value + ) + + @staticmethod + def transaction_date_now() -> datetime: + """Construct transaction datetime using the utc timezone.""" + return datetime.now(tz=timezone.utc) + + @staticmethod + def _get_account_name(): + """Return case statement for deriving payment account name.""" + return case( + (PaymentAccountModel.name.like('%-' + PaymentAccountModel.branch_name), + func.replace(PaymentAccountModel.name, '-' + PaymentAccountModel.branch_name, '') + ), else_=PaymentAccountModel.name).label('account_name') + + @classmethod + def search(cls, short_name_id: int, + search_criteria: EFTShortnameHistorySearch = EFTShortnameHistorySearch()): + """Return EFT Short name history by search criteria.""" + history_model = aliased(EFTShortnamesHistoricalModel) + latest_history_model = aliased(EFTShortnamesHistoricalModel) + + latest_history_subquery = ( + select( + latest_history_model.statement_number, + latest_history_model.transaction_type, + latest_history_model.is_processing + ) + .where( + latest_history_model.short_name_id == history_model.short_name_id, + latest_history_model.statement_number == history_model.statement_number, + latest_history_model.transaction_type != EFTHistoricalTypes.INVOICE_REFUND.value + ) + .order_by( + latest_history_model.statement_number, + latest_history_model.transaction_date.desc(), + latest_history_model.id.desc() + ) + .limit(1) + .correlate(history_model) + ).subquery('latest_statement_history') + + # Reversible if: + # - most recent state of the statement has been paid and not processing + # - within 60 days of that transaction date + # - is STATEMENT_PAID transaction type + reversible_statement_subquery = exists( + select(1) + .select_from(latest_history_subquery) + .where(and_(latest_history_subquery.c.transaction_type == EFTHistoricalTypes.STATEMENT_PAID.value, + latest_history_subquery.c.is_processing.is_(False))) + ) + + is_reversible_statement = case( + ( + and_( + history_model.transaction_type == EFTHistoricalTypes.STATEMENT_PAID.value, + history_model.transaction_date >= cls.transaction_date_now() - timedelta(days=60) + ), + reversible_statement_subquery + ), + else_=False + ) + + query = (db.session.query(history_model.id, + history_model.short_name_id, + history_model.amount, + history_model.credit_balance, + history_model.invoice_id, + history_model.statement_number, + history_model.transaction_date, + history_model.transaction_type, + history_model.is_processing, + PaymentAccountModel.auth_account_id, + cls._get_account_name(), + PaymentAccountModel.branch_name.label('account_branch'), + is_reversible_statement.label('is_reversible')) + .outerjoin(PaymentAccountModel, PaymentAccountModel.id == history_model.payment_account_id) + .filter(history_model.short_name_id == short_name_id) + .filter(history_model.hidden == false())) + + query = query.order_by(history_model.transaction_date.desc(), history_model.id.desc()) + + pagination = query.paginate(per_page=search_criteria.limit, page=search_criteria.page) + history_list = unstructure_schema_items(EFTShortnameHistorySchema, pagination.items) + + return { + 'page': search_criteria.page, + 'limit': search_criteria.limit, + 'items': history_list, + 'total': pagination.total + } diff --git a/pay-api/src/pay_api/services/eft_short_names.py b/pay-api/src/pay_api/services/eft_short_names.py index 4e1b44ba5..a8add0876 100644 --- a/pay-api/src/pay_api/services/eft_short_names.py +++ b/pay-api/src/pay_api/services/eft_short_names.py @@ -15,7 +15,7 @@ from __future__ import annotations from dataclasses import dataclass -from datetime import date, datetime +from datetime import date, datetime, timezone from typing import Dict, List, Optional from _decimal import Decimal @@ -30,12 +30,18 @@ from pay_api.models import EFTShortnameLinks as EFTShortnameLinksModel from pay_api.models import EFTShortnameLinkSchema from pay_api.models import EFTShortnames as EFTShortnameModel +from pay_api.models import EFTShortnamesHistorical as EFTShortnameHistoryModel from pay_api.models import EFTShortnameSchema from pay_api.models import Invoice as InvoiceModel from pay_api.models import PaymentAccount as PaymentAccountModel from pay_api.models import Statement as StatementModel from pay_api.models import StatementInvoices as StatementInvoicesModel from pay_api.models import db +from pay_api.services.auth import get_account_admin_users +from pay_api.services.eft_short_name_historical import EFTShortnameHistorical as EFTHistoryService +from pay_api.services.eft_short_name_historical import EFTShortnameHistory as EFTHistory +from pay_api.services.email_service import _render_payment_reversed_template, send_email +from pay_api.services.statement import Statement as StatementService from pay_api.utils.converter import Converter from pay_api.utils.enums import ( EFTCreditInvoiceStatus, EFTPaymentActions, EFTShortnameStatus, InvoiceStatus, PaymentMethod) @@ -43,8 +49,6 @@ from pay_api.utils.user_context import user_context from pay_api.utils.util import unstructure_schema_items -from .statement import Statement as StatementService - @dataclass class EFTShortnamesSearch: # pylint: disable=too-many-instance-attributes @@ -52,6 +56,7 @@ class EFTShortnamesSearch: # pylint: disable=too-many-instance-attributes id: Optional[int] = None account_id: Optional[str] = None + account_id_list: Optional[List[str]] = None allow_partial_account_id: Optional[bool] = True account_name: Optional[str] = None account_branch: Optional[str] = None @@ -93,6 +98,7 @@ def get_eft_credits(short_name_id: int) -> List[EFTCreditModel]: def _apply_eft_credit(cls, invoice_id: int, short_name_id: int, + link_group_id: int, auto_save: bool = False): """Apply EFT credit and update remaining credit records.""" invoice = InvoiceModel.find_by_id(invoice_id) @@ -107,12 +113,13 @@ def _apply_eft_credit(cls, if eft_credit_balance < invoice_balance: return - eft_credits: List[EFTCreditModel] = EFTShortnames.get_eft_credits(short_name_id) + eft_credits = EFTShortnames.get_eft_credits(short_name_id) for eft_credit in eft_credits: credit_invoice_link = EFTCreditInvoiceLinkModel( eft_credit_id=eft_credit.id, status_code=EFTCreditInvoiceStatus.PENDING.value, - invoice_id=invoice.id) + invoice_id=invoice.id, + link_group_id=link_group_id) if eft_credit.remaining_amount >= invoice_balance: # Credit covers the full invoice balance @@ -148,7 +155,7 @@ def process_payment_action(cls, short_name_id: int, request: Dict): raise BusinessException(Error.EFT_PAYMENT_ACTION_UNSUPPORTED) db.session.commit() - except Exception: # NOQA pylint:disable=broad-except + except Exception: # NOQA pylint:disable=broad-except db.session.rollback() raise current_app.logger.debug('>process_payment_action') @@ -168,6 +175,21 @@ def get_shortname_invoice_links(short_name_id: int, payment_account_id: int, credit_links_query = credit_links_query.filter_conditionally(invoice_id, InvoiceModel.id) return credit_links_query.all() + @staticmethod + def return_eft_credit(eft_credit_link: EFTCreditInvoiceLinkModel, + update_status: str = None) -> EFTCreditModel: + """Return EFT Credit Invoice Link amount to EFT Credit.""" + eft_credit = EFTCreditModel.find_by_id(eft_credit_link.eft_credit_id) + eft_credit.remaining_amount += eft_credit_link.amount + + if eft_credit.remaining_amount > eft_credit.amount: + raise BusinessException(Error.EFT_CREDIT_AMOUNT_UNEXPECTED) + + if update_status: + eft_credit_link.status_code = update_status + + return eft_credit + @classmethod def _cancel_payment_action(cls, short_name_id: int, auth_account_id: str, invoice_id: int = None): """Cancel EFT pending payments.""" @@ -180,17 +202,20 @@ def _cancel_payment_action(cls, short_name_id: int, auth_account_id: str, invoic payment_account_id=payment_account.id, invoice_id=invoice_id, statuses=[EFTCreditInvoiceStatus.PENDING.value]) + link_group_ids = set() for credit_link in credit_links: - eft_credit = EFTCreditModel.find_by_id(credit_link.eft_credit_id) - eft_credit.remaining_amount += credit_link.amount - - if eft_credit.remaining_amount > eft_credit.amount: - raise BusinessException(Error.EFT_CREDIT_AMOUNT_UNEXPECTED) - - credit_link.status_code = EFTCreditInvoiceStatus.CANCELLED.value + eft_credit = EFTShortnames.return_eft_credit(credit_link, EFTCreditInvoiceStatus.CANCELLED.value) + if credit_link.link_group_id: + link_group_ids.add(credit_link.link_group_id) db.session.add(eft_credit) db.session.add(credit_link) + # Clean up pending historical records from pending links + for link_group_id in link_group_ids: + history_model = EFTShortnameHistoryModel.find_by_related_group_link_id(link_group_id) + if history_model: + db.session.delete(history_model) + db.session.flush() current_app.logger.debug('>cancel_payment_action') @@ -201,27 +226,150 @@ def _apply_payment_action(cls, short_name_id: int, auth_account_id: str): if auth_account_id is None or PaymentAccountModel.find_by_auth_account_id(auth_account_id) is None: raise BusinessException(Error.EFT_PAYMENT_ACTION_ACCOUNT_ID_REQUIRED) - cls.process_owing_statements(short_name_id, auth_account_id) + cls._process_owing_statements(short_name_id, auth_account_id) current_app.logger.debug('>apply_payment_action') @classmethod - def _reverse_payment_action(cls, short_name_id: int, statement_id: int): # pylint: disable=unused-argument + def _get_statement_credit_invoice_links(cls, shortname_id, statement_id) -> List[EFTCreditInvoiceLinkModel]: + """Get most recent EFT Credit invoice links associated to a statement and short name.""" + query = (db.session.query(EFTCreditInvoiceLinkModel) + .distinct(EFTCreditInvoiceLinkModel.invoice_id) + .join(EFTCreditModel, EFTCreditModel.id == EFTCreditInvoiceLinkModel.eft_credit_id) + .join(StatementInvoicesModel, + StatementInvoicesModel.invoice_id == EFTCreditInvoiceLinkModel.invoice_id) + .filter(StatementInvoicesModel.statement_id == statement_id) + .filter(EFTCreditModel.short_name_id == shortname_id) + .filter(EFTCreditInvoiceLinkModel.status_code != EFTCreditInvoiceStatus.CANCELLED.value) + .order_by(EFTCreditInvoiceLinkModel.invoice_id.desc(), + EFTCreditInvoiceLinkModel.created_on.desc(), + EFTCreditInvoiceLinkModel.id.desc()) + ) + return query.all() + + @classmethod + def _validate_reversal_credit_invoice_links(cls, statement_id: int, + credit_invoice_links: List[EFTCreditInvoiceLinkModel]): + """Validate credit invoice links for reversal.""" + invalid_link_statuses = [EFTCreditInvoiceStatus.PENDING.value, + EFTCreditInvoiceStatus.PENDING_REFUND.value, + EFTCreditInvoiceStatus.REFUNDED.value] + + # We are reversing all invoices associated to a statement, if any links are in transition state or already + # refunded we should not allow a statement reversal + unprocessable_links = [link for link in credit_invoice_links if link.status_code in invalid_link_statuses] + if unprocessable_links: + raise BusinessException(Error.EFT_PAYMENT_ACTION_CREDIT_LINK_STATUS_INVALID) + # Validate when statement paid date can't be older than 60 days + min_payment_date = ( + db.session.query(func.min(InvoiceModel.payment_date)) + .join(StatementInvoicesModel, StatementInvoicesModel.invoice_id == InvoiceModel.id) + .filter(StatementInvoicesModel.statement_id == statement_id) + .filter(InvoiceModel.payment_method_code == PaymentMethod.EFT.value) + .scalar() + ) + + if min_payment_date is None: + raise BusinessException(Error.EFT_PAYMENT_ACTION_UNPAID_STATEMENT) + + date_difference = datetime.now(tz=timezone.utc) - min_payment_date.replace(tzinfo=timezone.utc) + if date_difference.days > 60: + raise BusinessException(Error.EFT_PAYMENT_ACTION_REVERSAL_EXCEEDS_SIXTY_DAYS) + + @classmethod + def _reverse_payment_action(cls, short_name_id: int, statement_id: int): """Reverse EFT Payments on a statement to short name EFT credits.""" current_app.logger.debug('