diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 233df140..1bab1630 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.2.0 +current_version = 0.3.0 commit = False tag = False allow_dirty = True diff --git a/.github/actions/build-rln-node/action.yml b/.github/actions/build-rln-node/action.yml index 9d490a08..7426aecc 100644 --- a/.github/actions/build-rln-node/action.yml +++ b/.github/actions/build-rln-node/action.yml @@ -12,7 +12,7 @@ runs: - name: Install Rust run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.81.0 + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y echo "$HOME/.cargo/bin" >> $GITHUB_PATH source "$HOME/.cargo/env" shell: bash diff --git a/.github/actions/publish-test-reports/action.yml b/.github/actions/publish-test-reports/action.yml index efabe347..2ee8090e 100644 --- a/.github/actions/publish-test-reports/action.yml +++ b/.github/actions/publish-test-reports/action.yml @@ -39,11 +39,16 @@ runs: shell: bash - name: Generate reports - run: ./.github/scripts/generate-reports.sh shell: bash + env: + RUN_ID: ${{ github.run_id }} + COMMIT_SHA: ${{ github.sha }} + run: ./.github/scripts/generate-reports.sh - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 with: - github_token: ${{ github.token }} - publish_dir: gh-pages + path: site + + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v4 diff --git a/.github/scripts/generate-reports.sh b/.github/scripts/generate-reports.sh index aedb582c..b3a46b6d 100755 --- a/.github/scripts/generate-reports.sh +++ b/.github/scripts/generate-reports.sh @@ -3,33 +3,33 @@ set -euo pipefail -mkdir -p gh-pages +mkdir -p site # Generate embedded report if [ -d "allure-results-embedded" ] && [ "$(ls -A allure-results-embedded 2>/dev/null)" ]; then - allure generate allure-results-embedded --clean -o gh-pages/embedded + allure generate allure-results-embedded --clean -o site/embedded else - mkdir -p gh-pages/embedded - echo "

No embedded test results available

" > gh-pages/embedded/index.html + mkdir -p site/embedded + echo "

No embedded test results available

" > site/embedded/index.html fi # Generate remote report if [ -d "allure-results-remote" ] && [ "$(ls -A allure-results-remote 2>/dev/null)" ]; then - allure generate allure-results-remote --clean -o gh-pages/remote + allure generate allure-results-remote --clean -o site/remote else - mkdir -p gh-pages/remote - echo "

No remote test results available

" > gh-pages/remote/index.html + mkdir -p site/remote + echo "

No remote test results available

" > site/remote/index.html fi # Copy coverage report if [ -d "coverage-report" ] && [ "$(ls -A coverage-report 2>/dev/null)" ]; then - cp -r coverage-report gh-pages/coverage + cp -r coverage-report site/coverage else - mkdir -p gh-pages/coverage - echo "

No coverage report available

" > gh-pages/coverage/index.html + mkdir -p site/coverage + echo "

No coverage report available

" > site/coverage/index.html fi # Create index page with links to all reports -cp .github/pages/allure-index.html gh-pages/index.html +cp .github/pages/allure-index.html site/index.html echo "✅ Reports generated successfully" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bbf3d73a..447a4cd7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -146,16 +146,16 @@ jobs: - test_ask_auth_for_imp_operations - test_settings - test_btc_channel_tx - - test_rgb20_channel_tx + - test_nia_channel_tx - test_help - test_about - test_backup_and_restore - - test_send_and_receive_rgb20_asset - - test_send_and_receive_rgb25_asset + - test_send_and_receive_nia_asset + - test_send_and_receive_cfa_asset - test_hide_exhausted_asset - test_fail_transfer - - test_issue_rgb20_asset - - test_issue_rgb25_asset + - test_issue_nia_asset + - test_issue_cfa_asset - test_view_unspent - test_refresh_transfer env: @@ -270,8 +270,12 @@ jobs: runs-on: ubuntu-latest if: always() permissions: - contents: write - actions: write + contents: read + pages: write + id-token: write + + environment: + name: github-pages steps: - name: Checkout repository diff --git a/accessible_constant.py b/accessible_constant.py index 130660ed..dcb92590 100644 --- a/accessible_constant.py +++ b/accessible_constant.py @@ -65,13 +65,13 @@ SEND_ASSET_BUTTON = 'send_asset_button' ASSET_ADDRESS_VALIDATION_LABEL = 'asset_address_validation_label' -# Issue RGB20 asset page -ISSUE_RGB20_ASSET = 'issue_rgb20_asset' -ISSUE_RGB20_ASSET_CLOSE_BUTTON = 'issue_rgb20_asset_close_button' -RGB20_ASSET_TICKER = 'rgb20_asset_ticker' -RGB20_ASSET_NAME = 'rgb20_asset_name' -RGB20_ASSET_AMOUNT = 'rgb20_asset_amount' -ISSUE_RGB20_BUTTON = 'issue_rgb20_button' +# Issue NIA asset page +ISSUE_NIA_ASSET = 'issue_nia_asset' +ISSUE_NIA_ASSET_CLOSE_BUTTON = 'issue_nia_asset_close_button' +NIA_ASSET_TICKER = 'nia_asset_ticker' +NIA_ASSET_NAME = 'nia_asset_name' +NIA_ASSET_AMOUNT = 'nia_asset_amount' +ISSUE_NIA_BUTTON = 'issue_nia_button' # Success page SUCCESS_PAGE_CLOSE_BUTTON = 'success_page_close_button' @@ -95,14 +95,14 @@ VIEW_UNSPENT_LIST_BUTTON = 'view_unspent_list_button' SETTINGS_BUTTON = 'settings_button' -# Issue RGB25 asset page -ISSUE_RGB25_ASSET = 'issue_rgb25_asset' -ISSUE_RGB25_BUTTON = 'issue_rgb25_button' -RGB25_ASSET_DESCRIPTION = 'rgb25_asset_description' -RGB25_ASSET_NAME = 'rgb25_asset_name' -RGB25_ASSET_AMOUNT = 'rgb25_asset_amount' -RGB25_UPLOAD_FILE_BUTTON = 'rgb25_upload_file_button' -ISSUE_RGB25_ASSET_CLOSE_BUTTON = 'issue_rgb25_asset_close_button' +# Issue CFA asset page +ISSUE_CFA_ASSET = 'issue_cfa_asset' +ISSUE_CFA_BUTTON = 'issue_cfa_button' +CFA_ASSET_DESCRIPTION = 'cfa_asset_description' +CFA_ASSET_NAME = 'cfa_asset_name' +CFA_ASSET_AMOUNT = 'cfa_asset_amount' +CFA_UPLOAD_FILE_BUTTON = 'cfa_upload_file_button' +ISSUE_CFA_ASSET_CLOSE_BUTTON = 'issue_cfa_asset_close_button' # File chooser FILE_CHOOSER = 'file chooser' diff --git a/e2e_tests/compose.yaml b/e2e_tests/compose.yaml index 19abc48e..c10175f6 100644 --- a/e2e_tests/compose.yaml +++ b/e2e_tests/compose.yaml @@ -1,6 +1,6 @@ services: bitcoind: - image: registry.gitlab.com/hashbeam/docker/bitcoind:28.1 + image: registry.gitlab.com/hashbeam/docker/bitcoind:30.0 command: "-fallbackfee=0.0002" environment: MYUID: 1000 @@ -11,7 +11,7 @@ services: volumes: - ./datacore:/srv/app/.bitcoin electrs: - image: registry.gitlab.com/hashbeam/docker/electrs:0.10.9 + image: registry.gitlab.com/hashbeam/docker/electrs:0.10.10 environment: MYUID: 1000 MYGID: 1000 diff --git a/e2e_tests/conftest.py b/e2e_tests/conftest.py index 8ed19bec..5182ea70 100644 --- a/e2e_tests/conftest.py +++ b/e2e_tests/conftest.py @@ -4,12 +4,17 @@ """ from __future__ import annotations +import io import os import subprocess import time +import allure import pytest from dogtail.tree import root +from PIL import Image +from Xlib import display +from Xlib import X from accessible_constant import DEFAULT_WALLET_MODES from src.model.enums.enums_model import WalletType @@ -132,6 +137,82 @@ def pytest_runtest_logreport(report): _print_test_result('⏭️', 'SKIPPED', test_name) +def _capture_screenshot(): + """ + Capture a screenshot of the entire screen using Xlib. + + Returns: + bytes: PNG image data, or None if capture fails + """ + try: + # Get the display and screen + dpy = display.Display() + screen = dpy.screen() + root_window = screen.root + + # Get screen dimensions + width = screen.width_in_pixels + height = screen.height_in_pixels + + # Capture the screen + raw_image = root_window.get_image( + 0, 0, width, height, X.ZPixmap, 0xffffffff, + ) + + # Convert to PIL Image + image = Image.frombytes( + 'RGB', + (width, height), + raw_image.data, + 'raw', + 'BGRX', + ) + + # Save to bytes buffer + buffer = io.BytesIO() + image.save(buffer, format='PNG') + buffer.seek(0) + + return buffer.getvalue() + except Exception as e: + print(f"[SCREENSHOT] Failed to capture screenshot: {e}") + return None + + +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item): + """ + Pytest hook to capture screenshots on test failure and attach to Allure report. + + This hook runs after each test phase (setup, call, teardown) and captures + a screenshot if the test failed during the 'call' phase. + """ + # Execute all other hooks to obtain the report object + outcome = yield + report = outcome.get_result() + + # Only capture screenshot on test failure during the 'call' phase + if report.when == 'call' and report.failed: + try: + screenshot_bytes = _capture_screenshot() + + if screenshot_bytes: + # Attach screenshot to Allure report + allure.attach( + screenshot_bytes, + name='failure_screenshot', + attachment_type=allure.attachment_type.PNG, + ) + print(f"""[SCREENSHOT] ✅ Screenshot captured and attached for failed test: + {item.nodeid}""") + else: + print(f"""[SCREENSHOT] ⚠️ Failed to capture screenshot for: + {item.nodeid}""") + except Exception as e: + print(f"""[SCREENSHOT] ❌ Error capturing screenshot for + {item.nodeid}: {e}""") + + def _is_ci_environment(): """Check if running in CI environment.""" return os.getenv('CI', '').lower() in ('true', '1', 'yes') diff --git a/e2e_tests/test/features/channel.py b/e2e_tests/test/features/channel.py index 25fd52fc..f23811db 100644 --- a/e2e_tests/test/features/channel.py +++ b/e2e_tests/test/features/channel.py @@ -1,6 +1,6 @@ # pylint:disable=too-many-branches """ -This module contains the IssueRgb20 class, which provides methods for issuing RGB20 assets. +This module contains the Channel class, which provides methods for channel operations. """ from __future__ import annotations @@ -144,7 +144,7 @@ def get_node_uri_for_embedded(self, application, ip_address): if self.do_is_displayed(self.sidebar_page_objects.fungibles_button()): self.sidebar_page_objects.click_fungibles_button() - embedded_node_uri = f"{node_pubkey}@{ip_address}:{ln_port}" + embedded_node_uri = f'{node_pubkey}@{ip_address}:{ln_port}' return embedded_node_uri @@ -163,6 +163,6 @@ def get_node_uri_for_remote(self, application, ip_address, ln_port): if self.do_is_displayed(self.sidebar_page_objects.fungibles_button()): self.sidebar_page_objects.click_fungibles_button() - remote_node_uri = f"{node_pubkey}@{ip_address}:{ln_port}" + remote_node_uri = f'{node_pubkey}@{ip_address}:{ln_port}' return remote_node_uri diff --git a/e2e_tests/test/features/issue_cfa.py b/e2e_tests/test/features/issue_cfa.py new file mode 100644 index 00000000..6858b04e --- /dev/null +++ b/e2e_tests/test/features/issue_cfa.py @@ -0,0 +1,145 @@ +""" +Module for testing CFA asset issuance. +""" +from __future__ import annotations + +import os + +from e2e_tests.test.pageobjects.main_page_objects import MainPageObjects +from e2e_tests.test.utilities.asset_copy import copy_cfa_image_to_home_directory +from e2e_tests.test.utilities.base_operation import BaseOperations + + +class IssueCfa(MainPageObjects, BaseOperations): + """ + Class for testing CFA asset issuance. + """ + + def __init__(self, application): + """ + Initialize the IssueCfa class. + """ + super().__init__(application) + + def issue_cfa_with_sufficient_sats_and_utxo(self, application, asset_name, asset_description, asset_amount, is_native_auth_enabled: bool = False): + """ + Issue CFA asset with sufficient sats and utxo. + """ + self.do_focus_on_application(application) + copy_cfa_image_to_home_directory(os.getcwd()) + + if self.do_is_displayed(self.sidebar_page_objects.collectibles_button()): + self.sidebar_page_objects.click_collectibles_button() + + if self.do_is_displayed(self.collectible_page_objects.issue_cfa_button()): + self.collectible_page_objects.click_issue_cfa_button() + + if self.do_is_displayed(self.issue_cfa_page_objects.asset_name()): + self.issue_cfa_page_objects.enter_asset_name(asset_name) + + if self.do_is_displayed(self.issue_cfa_page_objects.asset_description()): + self.issue_cfa_page_objects.enter_asset_description( + asset_description, + ) + + if self.do_is_displayed(self.issue_cfa_page_objects.asset_amount()): + self.issue_cfa_page_objects.enter_asset_amount(asset_amount) + + if self.do_is_displayed(self.issue_cfa_page_objects.upload_file_button()): + self.issue_cfa_page_objects.click_upload_file_button() + + if self.do_is_displayed(self.issue_cfa_page_objects.cfa_asset_media()): + self.issue_cfa_page_objects.click_cfa_asset_media() + + if self.do_is_displayed(self.issue_cfa_page_objects.issue_cfa_button()): + self.issue_cfa_page_objects.click_issue_cfa_button() + + if is_native_auth_enabled is True: + self.enter_native_password() + + if self.do_is_displayed(self.success_page_objects.home_button()): + self.success_page_objects.click_home_button() + + def issue_cfa_asset_without_sat(self, application, asset_name, asset_description, asset_amount): + """ + Issue CFA asset without sat. + """ + description = None + self.do_focus_on_application(application) + copy_cfa_image_to_home_directory(os.getcwd()) + + if self.do_is_displayed(self.sidebar_page_objects.collectibles_button()): + self.sidebar_page_objects.click_collectibles_button() + + if self.do_is_displayed(self.collectible_page_objects.issue_cfa_button()): + self.collectible_page_objects.click_issue_cfa_button() + + if self.do_is_displayed(self.issue_cfa_page_objects.asset_name()): + self.issue_cfa_page_objects.enter_asset_name(asset_name) + + if self.do_is_displayed(self.issue_cfa_page_objects.asset_description()): + self.issue_cfa_page_objects.enter_asset_description( + asset_description, + ) + + if self.do_is_displayed(self.issue_cfa_page_objects.asset_amount()): + self.issue_cfa_page_objects.enter_asset_amount(asset_amount) + + if self.do_is_displayed(self.issue_cfa_page_objects.upload_file_button()): + self.issue_cfa_page_objects.click_upload_file_button() + + if self.do_is_displayed(self.issue_cfa_page_objects.cfa_asset_media()): + self.issue_cfa_page_objects.click_cfa_asset_media() + + if self.do_is_displayed(self.issue_cfa_page_objects.issue_cfa_button()): + self.issue_cfa_page_objects.click_issue_cfa_button() + + if self.do_is_displayed(self.toaster_page_objects.toaster_frame()): + description = self.toaster_page_objects.click_and_get_description() + + if self.do_is_displayed(self.issue_cfa_page_objects.close_button()): + self.issue_cfa_page_objects.click_close_button() + + if self.do_is_displayed(self.sidebar_page_objects.fungibles_button()): + self.sidebar_page_objects.click_fungibles_button() + + return description + + def issue_cfa_with_sufficient_sats_and_no_utxo(self, application, asset_name, asset_description, asset_amount): + """ + Issue CFA asset with sufficient sats and no utxo. + """ + self.do_focus_on_application(application) + copy_cfa_image_to_home_directory(os.getcwd()) + + if self.do_is_displayed(self.sidebar_page_objects.view_unspents_button()): + self.sidebar_page_objects.click_view_unspents_button() + + if self.do_is_displayed(self.sidebar_page_objects.collectibles_button()): + self.sidebar_page_objects.click_collectibles_button() + + if self.do_is_displayed(self.collectible_page_objects.issue_cfa_button()): + self.collectible_page_objects.click_issue_cfa_button() + + if self.do_is_displayed(self.issue_cfa_page_objects.asset_name()): + self.issue_cfa_page_objects.enter_asset_name(asset_name) + + if self.do_is_displayed(self.issue_cfa_page_objects.asset_description()): + self.issue_cfa_page_objects.enter_asset_description( + asset_description, + ) + + if self.do_is_displayed(self.issue_cfa_page_objects.asset_amount()): + self.issue_cfa_page_objects.enter_asset_amount(asset_amount) + + if self.do_is_displayed(self.issue_cfa_page_objects.upload_file_button()): + self.issue_cfa_page_objects.click_upload_file_button() + + if self.do_is_displayed(self.issue_cfa_page_objects.cfa_asset_media()): + self.issue_cfa_page_objects.click_cfa_asset_media() + + if self.do_is_displayed(self.issue_cfa_page_objects.issue_cfa_button()): + self.issue_cfa_page_objects.click_issue_cfa_button() + + if self.do_is_displayed(self.success_page_objects.home_button()): + self.success_page_objects.click_home_button() diff --git a/e2e_tests/test/features/issue_nia.py b/e2e_tests/test/features/issue_nia.py new file mode 100644 index 00000000..8cba021a --- /dev/null +++ b/e2e_tests/test/features/issue_nia.py @@ -0,0 +1,101 @@ +""" +This module contains the IssueNia class, which provides methods for issuing NIA assets. +""" +from __future__ import annotations + +from e2e_tests.test.pageobjects.main_page_objects import MainPageObjects +from e2e_tests.test.utilities.base_operation import BaseOperations + + +class IssueNia(MainPageObjects, BaseOperations): + """ + This class provides methods for issuing NIA assets. + """ + + def __init__(self, application): + """ + Initializes the IssueNia class. + """ + super().__init__(application) + + def issue_nia_with_sufficient_sats_and_no_utxo(self, application, asset_ticker, asset_name, asset_amount): + """ + Issues an NIA asset with sufficient sats and no UTXO. + """ + self.do_focus_on_application(application) + + if self.do_is_displayed(self.sidebar_page_objects.fungibles_button()): + self.sidebar_page_objects.click_fungibles_button() + + if self.do_is_displayed(self.fungible_page_objects.issue_nia_button()): + self.fungible_page_objects.click_issue_nia_button() + + if self.do_is_displayed(self.issue_nia_page_objects.asset_ticker()): + self.issue_nia_page_objects.enter_asset_ticker(asset_ticker) + + if self.do_is_displayed(self.issue_nia_page_objects.asset_name()): + self.issue_nia_page_objects.enter_asset_name(asset_name) + + if self.do_is_displayed(self.issue_nia_page_objects.asset_amount()): + self.issue_nia_page_objects.enter_asset_amount(asset_amount) + + if self.do_is_displayed(self.issue_nia_page_objects.issue_nia_button()): + self.issue_nia_page_objects.click_issue_nia_button() + + if self.do_is_displayed(self.success_page_objects.home_button()): + self.success_page_objects.click_home_button() + + def issue_nia_asset_without_sat(self, application, asset_ticker, asset_name, asset_amount): + """ + Issues an NIA asset without sufficient sats. + """ + description = None + self.do_focus_on_application(application) + if self.do_is_displayed(self.fungible_page_objects.issue_nia_button()): + self.fungible_page_objects.click_issue_nia_button() + + if self.do_is_displayed(self.issue_nia_page_objects.asset_ticker()): + self.issue_nia_page_objects.enter_asset_ticker(asset_ticker) + + if self.do_is_displayed(self.issue_nia_page_objects.asset_name()): + self.issue_nia_page_objects.enter_asset_name(asset_name) + + if self.do_is_displayed(self.issue_nia_page_objects.asset_amount()): + self.issue_nia_page_objects.enter_asset_amount(asset_amount) + + if self.do_is_displayed(self.issue_nia_page_objects.issue_nia_button()): + self.issue_nia_page_objects.click_issue_nia_button() + + if self.do_is_displayed(self.toaster_page_objects.toaster_frame()): + description = self.toaster_page_objects.click_and_get_description() + + if self.do_is_displayed(self.issue_nia_page_objects.close_button()): + self.issue_nia_page_objects.click_close_button() + + return description + + def issue_nia_with_sufficient_sats_and_utxo(self, application, asset_ticker, asset_name, asset_amount, is_native_auth_enabled: bool = False): + """ + Issues an NIA asset with sufficient sats and UTXO. + """ + self.do_focus_on_application(application) + if self.do_is_displayed(self.fungible_page_objects.issue_nia_button()): + self.fungible_page_objects.click_issue_nia_button() + + if self.do_is_displayed(self.issue_nia_page_objects.asset_ticker()): + self.issue_nia_page_objects.enter_asset_ticker(asset_ticker) + + if self.do_is_displayed(self.issue_nia_page_objects.asset_name()): + self.issue_nia_page_objects.enter_asset_name(asset_name) + + if self.do_is_displayed(self.issue_nia_page_objects.asset_amount()): + self.issue_nia_page_objects.enter_asset_amount(asset_amount) + + if self.do_is_displayed(self.issue_nia_page_objects.issue_nia_button()): + self.issue_nia_page_objects.click_issue_nia_button() + + if is_native_auth_enabled is True: + self.enter_native_password() + + if self.do_is_displayed(self.success_page_objects.home_button()): + self.success_page_objects.click_home_button() diff --git a/e2e_tests/test/features/issue_rgb20.py b/e2e_tests/test/features/issue_rgb20.py deleted file mode 100644 index 050ab894..00000000 --- a/e2e_tests/test/features/issue_rgb20.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -This module contains the IssueRgb20 class, which provides methods for issuing RGB20 assets. -""" -from __future__ import annotations - -from e2e_tests.test.pageobjects.main_page_objects import MainPageObjects -from e2e_tests.test.utilities.base_operation import BaseOperations - - -class IssueRgb20(MainPageObjects, BaseOperations): - """ - This class provides methods for issuing RGB20 assets. - """ - - def __init__(self, application): - """ - Initializes the IssueRgb20 class. - """ - super().__init__(application) - - def issue_rgb20_with_sufficient_sats_and_no_utxo(self, application, asset_ticker, asset_name, asset_amount): - """ - Issues an RGB20 asset with sufficient sats and no UTXO. - """ - self.do_focus_on_application(application) - - if self.do_is_displayed(self.sidebar_page_objects.fungibles_button()): - self.sidebar_page_objects.click_fungibles_button() - - if self.do_is_displayed(self.fungible_page_objects.issue_rgb20_button()): - self.fungible_page_objects.click_issue_rgb20_button() - - if self.do_is_displayed(self.issue_rgb20_page_objects.asset_ticker()): - self.issue_rgb20_page_objects.enter_asset_ticker(asset_ticker) - - if self.do_is_displayed(self.issue_rgb20_page_objects.asset_name()): - self.issue_rgb20_page_objects.enter_asset_name(asset_name) - - if self.do_is_displayed(self.issue_rgb20_page_objects.asset_amount()): - self.issue_rgb20_page_objects.enter_asset_amount(asset_amount) - - if self.do_is_displayed(self.issue_rgb20_page_objects.issue_rgb20_button()): - self.issue_rgb20_page_objects.click_issue_rgb20_button() - - if self.do_is_displayed(self.success_page_objects.home_button()): - self.success_page_objects.click_home_button() - - def issue_rgb20_asset_without_sat(self, application, asset_ticker, asset_name, asset_amount): - """ - Issues an RGB20 asset without sufficient sats. - """ - description = None - self.do_focus_on_application(application) - if self.do_is_displayed(self.fungible_page_objects.issue_rgb20_button()): - self.fungible_page_objects.click_issue_rgb20_button() - - if self.do_is_displayed(self.issue_rgb20_page_objects.asset_ticker()): - self.issue_rgb20_page_objects.enter_asset_ticker(asset_ticker) - - if self.do_is_displayed(self.issue_rgb20_page_objects.asset_name()): - self.issue_rgb20_page_objects.enter_asset_name(asset_name) - - if self.do_is_displayed(self.issue_rgb20_page_objects.asset_amount()): - self.issue_rgb20_page_objects.enter_asset_amount(asset_amount) - - if self.do_is_displayed(self.issue_rgb20_page_objects.issue_rgb20_button()): - self.issue_rgb20_page_objects.click_issue_rgb20_button() - - if self.do_is_displayed(self.toaster_page_objects.toaster_frame()): - description = self.toaster_page_objects.click_and_get_description() - - if self.do_is_displayed(self.issue_rgb20_page_objects.close_button()): - self.issue_rgb20_page_objects.click_close_button() - - return description - - def issue_rgb20_with_sufficient_sats_and_utxo(self, application, asset_ticker, asset_name, asset_amount, is_native_auth_enabled: bool = False): - """ - Issues an RGB20 asset with sufficient sats and UTXO. - """ - self.do_focus_on_application(application) - if self.do_is_displayed(self.fungible_page_objects.issue_rgb20_button()): - self.fungible_page_objects.click_issue_rgb20_button() - - if self.do_is_displayed(self.issue_rgb20_page_objects.asset_ticker()): - self.issue_rgb20_page_objects.enter_asset_ticker(asset_ticker) - - if self.do_is_displayed(self.issue_rgb20_page_objects.asset_name()): - self.issue_rgb20_page_objects.enter_asset_name(asset_name) - - if self.do_is_displayed(self.issue_rgb20_page_objects.asset_amount()): - self.issue_rgb20_page_objects.enter_asset_amount(asset_amount) - - if self.do_is_displayed(self.issue_rgb20_page_objects.issue_rgb20_button()): - self.issue_rgb20_page_objects.click_issue_rgb20_button() - - if is_native_auth_enabled is True: - self.enter_native_password() - - if self.do_is_displayed(self.success_page_objects.home_button()): - self.success_page_objects.click_home_button() diff --git a/e2e_tests/test/features/issue_rgb25.py b/e2e_tests/test/features/issue_rgb25.py deleted file mode 100644 index 0ddd8d6f..00000000 --- a/e2e_tests/test/features/issue_rgb25.py +++ /dev/null @@ -1,145 +0,0 @@ -""" -Module for testing RGB25 asset issuance. -""" -from __future__ import annotations - -import os - -from e2e_tests.test.pageobjects.main_page_objects import MainPageObjects -from e2e_tests.test.utilities.asset_copy import copy_rgb25_image_to_home_directory -from e2e_tests.test.utilities.base_operation import BaseOperations - - -class IssueRgb25(MainPageObjects, BaseOperations): - """ - Class for testing RGB25 asset issuance. - """ - - def __init__(self, application): - """ - Initialize the IssueRgb25 class. - """ - super().__init__(application) - - def issue_rgb25_with_sufficient_sats_and_utxo(self, application, asset_name, asset_description, asset_amount, is_native_auth_enabled: bool = False): - """ - Issue RGB25 asset with sufficient sats and utxo. - """ - self.do_focus_on_application(application) - copy_rgb25_image_to_home_directory(os.getcwd()) - - if self.do_is_displayed(self.sidebar_page_objects.collectibles_button()): - self.sidebar_page_objects.click_collectibles_button() - - if self.do_is_displayed(self.collectible_page_objects.issue_rgb25_button()): - self.collectible_page_objects.click_issue_rgb25_button() - - if self.do_is_displayed(self.issue_rgb25_page_objects.asset_name()): - self.issue_rgb25_page_objects.enter_asset_name(asset_name) - - if self.do_is_displayed(self.issue_rgb25_page_objects.asset_description()): - self.issue_rgb25_page_objects.enter_asset_description( - asset_description, - ) - - if self.do_is_displayed(self.issue_rgb25_page_objects.asset_amount()): - self.issue_rgb25_page_objects.enter_asset_amount(asset_amount) - - if self.do_is_displayed(self.issue_rgb25_page_objects.upload_file_button()): - self.issue_rgb25_page_objects.click_upload_file_button() - - if self.do_is_displayed(self.issue_rgb25_page_objects.rgb25_asset_media()): - self.issue_rgb25_page_objects.click_rgb25_asset_media() - - if self.do_is_displayed(self.issue_rgb25_page_objects.issue_rgb25_button()): - self.issue_rgb25_page_objects.click_issue_rgb25_button() - - if is_native_auth_enabled is True: - self.enter_native_password() - - if self.do_is_displayed(self.success_page_objects.home_button()): - self.success_page_objects.click_home_button() - - def issue_rgb25_asset_without_sat(self, application, asset_name, asset_description, asset_amount): - """ - Issue RGB25 asset without sat. - """ - description = None - self.do_focus_on_application(application) - copy_rgb25_image_to_home_directory(os.getcwd()) - - if self.do_is_displayed(self.sidebar_page_objects.collectibles_button()): - self.sidebar_page_objects.click_collectibles_button() - - if self.do_is_displayed(self.collectible_page_objects.issue_rgb25_button()): - self.collectible_page_objects.click_issue_rgb25_button() - - if self.do_is_displayed(self.issue_rgb25_page_objects.asset_name()): - self.issue_rgb25_page_objects.enter_asset_name(asset_name) - - if self.do_is_displayed(self.issue_rgb25_page_objects.asset_description()): - self.issue_rgb25_page_objects.enter_asset_description( - asset_description, - ) - - if self.do_is_displayed(self.issue_rgb25_page_objects.asset_amount()): - self.issue_rgb25_page_objects.enter_asset_amount(asset_amount) - - if self.do_is_displayed(self.issue_rgb25_page_objects.upload_file_button()): - self.issue_rgb25_page_objects.click_upload_file_button() - - if self.do_is_displayed(self.issue_rgb25_page_objects.rgb25_asset_media()): - self.issue_rgb25_page_objects.click_rgb25_asset_media() - - if self.do_is_displayed(self.issue_rgb25_page_objects.issue_rgb25_button()): - self.issue_rgb25_page_objects.click_issue_rgb25_button() - - if self.do_is_displayed(self.toaster_page_objects.toaster_frame()): - description = self.toaster_page_objects.click_and_get_description() - - if self.do_is_displayed(self.issue_rgb25_page_objects.close_button()): - self.issue_rgb25_page_objects.click_close_button() - - if self.do_is_displayed(self.sidebar_page_objects.fungibles_button()): - self.sidebar_page_objects.click_fungibles_button() - - return description - - def issue_rgb25_with_sufficient_sats_and_no_utxo(self, application, asset_name, asset_description, asset_amount): - """ - Issue RGB25 asset with sufficient sats and no utxo. - """ - self.do_focus_on_application(application) - copy_rgb25_image_to_home_directory(os.getcwd()) - - if self.do_is_displayed(self.sidebar_page_objects.view_unspents_button()): - self.sidebar_page_objects.click_view_unspents_button() - - if self.do_is_displayed(self.sidebar_page_objects.collectibles_button()): - self.sidebar_page_objects.click_collectibles_button() - - if self.do_is_displayed(self.collectible_page_objects.issue_rgb25_button()): - self.collectible_page_objects.click_issue_rgb25_button() - - if self.do_is_displayed(self.issue_rgb25_page_objects.asset_name()): - self.issue_rgb25_page_objects.enter_asset_name(asset_name) - - if self.do_is_displayed(self.issue_rgb25_page_objects.asset_description()): - self.issue_rgb25_page_objects.enter_asset_description( - asset_description, - ) - - if self.do_is_displayed(self.issue_rgb25_page_objects.asset_amount()): - self.issue_rgb25_page_objects.enter_asset_amount(asset_amount) - - if self.do_is_displayed(self.issue_rgb25_page_objects.upload_file_button()): - self.issue_rgb25_page_objects.click_upload_file_button() - - if self.do_is_displayed(self.issue_rgb25_page_objects.rgb25_asset_media()): - self.issue_rgb25_page_objects.click_rgb25_asset_media() - - if self.do_is_displayed(self.issue_rgb25_page_objects.issue_rgb25_button()): - self.issue_rgb25_page_objects.click_issue_rgb25_button() - - if self.do_is_displayed(self.success_page_objects.home_button()): - self.success_page_objects.click_home_button() diff --git a/e2e_tests/test/features/main_features.py b/e2e_tests/test/features/main_features.py index dd00d9e6..4896dd36 100644 --- a/e2e_tests/test/features/main_features.py +++ b/e2e_tests/test/features/main_features.py @@ -5,8 +5,8 @@ from __future__ import annotations from e2e_tests.test.features.channel import Channel -from e2e_tests.test.features.issue_rgb20 import IssueRgb20 -from e2e_tests.test.features.issue_rgb25 import IssueRgb25 +from e2e_tests.test.features.issue_cfa import IssueCfa +from e2e_tests.test.features.issue_nia import IssueNia from e2e_tests.test.features.receive import ReceiveOperation from e2e_tests.test.features.send import SendOperation from e2e_tests.test.features.wallet import Wallet @@ -25,9 +25,9 @@ def __init__(self, application): self.wallet_features = Wallet(self.application) - self.issue_rgb20_features = IssueRgb20(self.application) + self.issue_nia_features = IssueNia(self.application) - self.issue_rgb25_features = IssueRgb25(self.application) + self.issue_cfa_features = IssueCfa(self.application) self.receive_features = ReceiveOperation(self.application) diff --git a/e2e_tests/test/features/wallet.py b/e2e_tests/test/features/wallet.py index 8f0ddcdb..d6048bdc 100644 --- a/e2e_tests/test/features/wallet.py +++ b/e2e_tests/test/features/wallet.py @@ -122,7 +122,7 @@ def remote_wallet(self, application, url): if self.do_is_displayed(self.set_password_page_objects.proceed_button()): self.set_password_page_objects.click_proceed_button() - def create_and_fund_wallet(self, wallets_and_operations, application, application_url, fund=True): + def create_and_fund_wallet(self, wallets_and_operations, application, application_url=None, fund=True): """ Create a new wallet and fund it. """ diff --git a/e2e_tests/test/pageobjects/collectible_page.py b/e2e_tests/test/pageobjects/collectible_page.py index d58dd1b8..e9723d95 100644 --- a/e2e_tests/test/pageobjects/collectible_page.py +++ b/e2e_tests/test/pageobjects/collectible_page.py @@ -3,7 +3,7 @@ """ from __future__ import annotations -from accessible_constant import ISSUE_RGB25_ASSET +from accessible_constant import ISSUE_CFA_ASSET from e2e_tests.test.utilities.base_operation import BaseOperations @@ -16,32 +16,32 @@ def __init__(self, application): """ super().__init__(application) - self.rgb25_asset_name = None + self.cfa_asset_name = None # Define elements using lambdas for lazy evaluation - self.issue_rgb25_button = lambda: self.perform_action_on_element( - role_name='push button', name=ISSUE_RGB25_ASSET, + self.issue_cfa_button = lambda: self.perform_action_on_element( + role_name='push button', name=ISSUE_CFA_ASSET, ) - def click_issue_rgb25_button(self): + def click_issue_cfa_button(self): """ - Click the issue RGB25 button if it's displayed. + Click the issue CFA button if it's displayed. """ - return self.do_click(self.issue_rgb25_button()) if self.do_is_displayed(self.issue_rgb25_button()) else None + return self.do_click(self.issue_cfa_button()) if self.do_is_displayed(self.issue_cfa_button()) else None - def get_rgb25_asset_name(self, asset_name): + def get_cfa_asset_name(self, asset_name): """ Get the asset name if it's displayed. """ - self.rgb25_asset_name = self.perform_action_on_element( + self.cfa_asset_name = self.perform_action_on_element( role_name='label', name=asset_name, ) - return self.do_get_text(self.rgb25_asset_name) if self.do_is_displayed(self.rgb25_asset_name) else None + return self.do_get_text(self.cfa_asset_name) if self.do_is_displayed(self.cfa_asset_name) else None - def click_rgb25_frame(self, asset_name): + def click_cfa_frame(self, asset_name): """ - Click the RGB25 frame if it's displayed. + Click the CFA frame if it's displayed. """ - self.rgb25_asset_name = self.perform_action_on_element( + self.cfa_asset_name = self.perform_action_on_element( role_name='label', name=asset_name, ) - return self.do_click(self.rgb25_asset_name) if self.do_is_displayed(self.rgb25_asset_name) else None + return self.do_click(self.cfa_asset_name) if self.do_is_displayed(self.cfa_asset_name) else None diff --git a/e2e_tests/test/pageobjects/fungible_page.py b/e2e_tests/test/pageobjects/fungible_page.py index 0fbbece8..bdea42af 100644 --- a/e2e_tests/test/pageobjects/fungible_page.py +++ b/e2e_tests/test/pageobjects/fungible_page.py @@ -4,7 +4,7 @@ from __future__ import annotations from accessible_constant import FUNGIBLES_SCROLL_WIDGETS -from accessible_constant import ISSUE_RGB20_ASSET +from accessible_constant import ISSUE_NIA_ASSET from accessible_constant import NETWORK_AND_BACKUP_FRAME from e2e_tests.test.utilities.base_operation import BaseOperations @@ -22,9 +22,9 @@ def __init__(self, application): super().__init__(application) # Define elements using lambdas for lazy evaluation - self.rgb_20_asset_name = None - self.issue_rgb20_button = lambda: self.perform_action_on_element( - role_name='push button', name=ISSUE_RGB20_ASSET, + self.nia_asset_name = None + self.issue_nia_button = lambda: self.perform_action_on_element( + role_name='push button', name=ISSUE_NIA_ASSET, ) self.bitcoin_frame = lambda: self.perform_action_on_element( role_name='label', name='regtest bitcoin', @@ -40,14 +40,14 @@ def get_child_count(self): """Returns child count""" return self.do_get_child_count(self.fungibles_scroll_area()) if self.do_is_displayed(self.fungibles_scroll_area()) else None - def click_issue_rgb20_button(self): + def click_issue_nia_button(self): """ - Clicks the issue RGB20 button if it is displayed. + Clicks the issue NIA button if it is displayed. Returns: bool: True if the button is clicked, None otherwise. """ - return self.do_click(self.issue_rgb20_button()) if self.do_is_displayed(self.issue_rgb20_button()) else None + return self.do_click(self.issue_nia_button()) if self.do_is_displayed(self.issue_nia_button()) else None def click_bitcoin_frame(self): """ @@ -58,7 +58,7 @@ def click_bitcoin_frame(self): """ return self.do_click(self.bitcoin_frame()) if self.do_is_displayed(self.bitcoin_frame()) else None - def get_rgb20_asset_name(self, asset_name): + def get_nia_asset_name(self, asset_name): """ Retrieves the asset name if it is displayed. @@ -68,14 +68,14 @@ def get_rgb20_asset_name(self, asset_name): Returns: str: The asset name if it is displayed, None otherwise. """ - self.rgb_20_asset_name = self.perform_action_on_element( + self.nia_asset_name = self.perform_action_on_element( role_name='label', name=asset_name, ) - return self.do_get_text(self.rgb_20_asset_name) if self.do_is_displayed(self.rgb_20_asset_name) else None + return self.do_get_text(self.nia_asset_name) if self.do_is_displayed(self.nia_asset_name) else None - def click_rgb20_frame(self, asset_name): + def click_nia_frame(self, asset_name): """ - Clicks the RGB20 frame if it is displayed. + Clicks the NIA frame if it is displayed. Args: asset_name (str): The name of the asset. @@ -83,10 +83,10 @@ def click_rgb20_frame(self, asset_name): Returns: bool: True if the frame is clicked, None otherwise. """ - self.rgb_20_asset_name = self.perform_action_on_element( + self.nia_asset_name = self.perform_action_on_element( role_name='label', name=asset_name, ) - return self.do_click(self.rgb_20_asset_name) if self.do_is_displayed(self.rgb_20_asset_name) else None + return self.do_click(self.nia_asset_name) if self.do_is_displayed(self.nia_asset_name) else None def get_backup_tooltip(self): """ diff --git a/e2e_tests/test/pageobjects/issue_rgb25_page.py b/e2e_tests/test/pageobjects/issue_cfa_page.py similarity index 62% rename from e2e_tests/test/pageobjects/issue_rgb25_page.py rename to e2e_tests/test/pageobjects/issue_cfa_page.py index e07c28fa..c701c922 100644 --- a/e2e_tests/test/pageobjects/issue_rgb25_page.py +++ b/e2e_tests/test/pageobjects/issue_cfa_page.py @@ -1,24 +1,24 @@ """ -IssueRgb25PageObjects class provides methods to interact with the issue RGB25 page. +IssueCfaPageObjects class provides methods to interact with the issue CFA page. """ from __future__ import annotations from dogtail.rawinput import keyCombo from dogtail.tree import root +from accessible_constant import CFA_ASSET_AMOUNT +from accessible_constant import CFA_ASSET_DESCRIPTION +from accessible_constant import CFA_ASSET_NAME +from accessible_constant import CFA_UPLOAD_FILE_BUTTON from accessible_constant import FILE_CHOOSER -from accessible_constant import ISSUE_RGB25_ASSET_CLOSE_BUTTON -from accessible_constant import ISSUE_RGB25_BUTTON -from accessible_constant import RGB25_ASSET_AMOUNT -from accessible_constant import RGB25_ASSET_DESCRIPTION -from accessible_constant import RGB25_ASSET_NAME -from accessible_constant import RGB25_UPLOAD_FILE_BUTTON +from accessible_constant import ISSUE_CFA_ASSET_CLOSE_BUTTON +from accessible_constant import ISSUE_CFA_BUTTON from e2e_tests.test.utilities.base_operation import BaseOperations -class IssueRgb25PageObjects(BaseOperations): +class IssueCfaPageObjects(BaseOperations): """ - Initialize the IssueRgb25PageObjects class. + Initialize the IssueCfaPageObjects class. Args: application: The application instance. @@ -26,7 +26,7 @@ class IssueRgb25PageObjects(BaseOperations): def __init__(self, application): """ - Initialize the IssueRgb25PageObjects class. + Initialize the IssueCfaPageObjects class. Args: application: The application instance. @@ -34,31 +34,31 @@ def __init__(self, application): super().__init__(application) self.close_button = lambda: self.perform_action_on_element( - role_name='push button', name=ISSUE_RGB25_ASSET_CLOSE_BUTTON, + role_name='push button', name=ISSUE_CFA_ASSET_CLOSE_BUTTON, ) self.asset_name = lambda: self.perform_action_on_element( - role_name='text', name=RGB25_ASSET_NAME, + role_name='text', name=CFA_ASSET_NAME, ) self.asset_description = lambda: self.perform_action_on_element( - role_name='text', name=RGB25_ASSET_DESCRIPTION, + role_name='text', name=CFA_ASSET_DESCRIPTION, ) self.asset_amount = lambda: self.perform_action_on_element( - role_name='text', name=RGB25_ASSET_AMOUNT, + role_name='text', name=CFA_ASSET_AMOUNT, ) - self.issue_rgb25_button = lambda: self.perform_action_on_element( - role_name='push button', name=ISSUE_RGB25_BUTTON, + self.issue_cfa_button = lambda: self.perform_action_on_element( + role_name='push button', name=ISSUE_CFA_BUTTON, ) self.upload_file_button = lambda: self.perform_action_on_element( - role_name='push button', name=RGB25_UPLOAD_FILE_BUTTON, + role_name='push button', name=CFA_UPLOAD_FILE_BUTTON, ) self.file_dialog = lambda: root.child(roleName=FILE_CHOOSER) - self.rgb25_asset_media = lambda: self.file_dialog().child( + self.cfa_asset_media = lambda: self.file_dialog().child( roleName='table cell', name='sample.png', ) def click_close_button(self): """ - Click the close button on the issue RGB25 page. + Click the close button on the issue CFA page. Returns: bool: True if the button is clicked, None otherwise. @@ -67,7 +67,7 @@ def click_close_button(self): def enter_asset_name(self, asset_name): """ - Enter the asset name on the issue RGB25 page. + Enter the asset name on the issue CFA page. Args: asset_name (str): The asset name to enter. @@ -79,7 +79,7 @@ def enter_asset_name(self, asset_name): def enter_asset_description(self, asset_description): """ - Enter the asset description on the issue RGB25 page. + Enter the asset description on the issue CFA page. Args: asset_description (str): The asset description to enter. @@ -91,7 +91,7 @@ def enter_asset_description(self, asset_description): def enter_asset_amount(self, asset_amount): """ - Enter the asset amount on the issue RGB25 page. + Enter the asset amount on the issue CFA page. Args: asset_amount (str): The asset amount to enter. @@ -101,33 +101,33 @@ def enter_asset_amount(self, asset_amount): """ return self.do_set_value(self.asset_amount(), asset_amount) if self.do_is_displayed(self.asset_amount()) else None - def click_issue_rgb25_button(self): + def click_issue_cfa_button(self): """ - Click the issue RGB25 button on the issue RGB25 page. + Click the issue CFA button on the issue CFA page. Returns: bool: True if the button is clicked, None otherwise. """ - return self.do_click(self.issue_rgb25_button()) if self.do_is_displayed(self.issue_rgb25_button()) else None + return self.do_click(self.issue_cfa_button()) if self.do_is_displayed(self.issue_cfa_button()) else None def click_upload_file_button(self): """ - Click the upload file button on the issue RGB25 page. + Click the upload file button on the issue CFA page. Returns: bool: True if the button is clicked, None otherwise. """ return self.do_click(self.upload_file_button()) if self.do_is_displayed(self.upload_file_button()) else None - def click_rgb25_asset_media(self): + def click_cfa_asset_media(self): """ - Select the RGB25 asset by focusing on it and pressing Enter. + Select the CFA asset by focusing on it and pressing Enter. Returns: bool: True if the asset is selected, False otherwise. """ - if self.do_is_displayed(self.rgb25_asset_media()): - self.rgb25_asset_media().grabFocus() + if self.do_is_displayed(self.cfa_asset_media()): + self.cfa_asset_media().grabFocus() keyCombo('enter') return True return False # Return False if the element is not displayed diff --git a/e2e_tests/test/pageobjects/issue_rgb20_page.py b/e2e_tests/test/pageobjects/issue_nia_page.py similarity index 59% rename from e2e_tests/test/pageobjects/issue_rgb20_page.py rename to e2e_tests/test/pageobjects/issue_nia_page.py index d2eddcb8..9c04e674 100644 --- a/e2e_tests/test/pageobjects/issue_rgb20_page.py +++ b/e2e_tests/test/pageobjects/issue_nia_page.py @@ -1,24 +1,24 @@ """ -IssueRgb20PageObjects class provides methods to interact with issue RGB20 page elements. +IssueNiaPageObjects class provides methods to interact with issue NIA page elements. """ from __future__ import annotations -from accessible_constant import ISSUE_RGB20_ASSET_CLOSE_BUTTON -from accessible_constant import ISSUE_RGB20_BUTTON -from accessible_constant import RGB20_ASSET_AMOUNT -from accessible_constant import RGB20_ASSET_NAME -from accessible_constant import RGB20_ASSET_TICKER +from accessible_constant import ISSUE_NIA_ASSET_CLOSE_BUTTON +from accessible_constant import ISSUE_NIA_BUTTON +from accessible_constant import NIA_ASSET_AMOUNT +from accessible_constant import NIA_ASSET_NAME +from accessible_constant import NIA_ASSET_TICKER from e2e_tests.test.utilities.base_operation import BaseOperations -class IssueRgb20PageObjects(BaseOperations): +class IssueNiaPageObjects(BaseOperations): """ - IssueRgb20PageObjects class provides methods to interact with issue RGB20 page elements. + IssueNiaPageObjects class provides methods to interact with issue NIA page elements. """ def __init__(self, application): """ - Initializes the IssueRgb20PageObjects class with the application object. + Initializes the IssueNiaPageObjects class with the application object. Args: application (object): The application object. @@ -26,24 +26,24 @@ def __init__(self, application): super().__init__(application) self.close_button = lambda: self.perform_action_on_element( - role_name='push button', name=ISSUE_RGB20_ASSET_CLOSE_BUTTON, + role_name='push button', name=ISSUE_NIA_ASSET_CLOSE_BUTTON, ) self.asset_name = lambda: self.perform_action_on_element( - role_name='text', name=RGB20_ASSET_NAME, + role_name='text', name=NIA_ASSET_NAME, ) self.asset_ticker = lambda: self.perform_action_on_element( - role_name='text', name=RGB20_ASSET_TICKER, + role_name='text', name=NIA_ASSET_TICKER, ) self.asset_amount = lambda: self.perform_action_on_element( - role_name='text', name=RGB20_ASSET_AMOUNT, + role_name='text', name=NIA_ASSET_AMOUNT, ) - self.issue_rgb20_button = lambda: self.perform_action_on_element( - role_name='push button', name=ISSUE_RGB20_BUTTON, + self.issue_nia_button = lambda: self.perform_action_on_element( + role_name='push button', name=ISSUE_NIA_BUTTON, ) def click_close_button(self): """ - Clicks the close button on the issue RGB20 page. + Clicks the close button on the issue NIA page. Returns: bool: True if the button is clicked, False otherwise. @@ -52,7 +52,7 @@ def click_close_button(self): def enter_asset_name(self, asset_name): """ - Enters the asset name on the issue RGB20 page. + Enters the asset name on the issue NIA page. Args: asset_name (str): The asset name to enter. @@ -64,7 +64,7 @@ def enter_asset_name(self, asset_name): def enter_asset_ticker(self, asset_ticker): """ - Enters the asset ticker on the issue RGB20 page. + Enters the asset ticker on the issue NIA page. Args: asset_ticker (str): The asset ticker to enter. @@ -76,7 +76,7 @@ def enter_asset_ticker(self, asset_ticker): def enter_asset_amount(self, asset_amount): """ - Enters the asset amount on the issue RGB20 page. + Enters the asset amount on the issue NIA page. Args: asset_amount (str): The asset amount to enter. @@ -86,11 +86,11 @@ def enter_asset_amount(self, asset_amount): """ return self.do_set_value(self.asset_amount(), asset_amount) if self.do_is_displayed(self.asset_amount()) else None - def click_issue_rgb20_button(self): + def click_issue_nia_button(self): """ - Clicks the issue RGB20 button on the issue RGB20 page. + Clicks the issue NIA button on the issue NIA page. Returns: bool: True if the button is clicked, False otherwise. """ - return self.do_click(self.issue_rgb20_button()) if self.do_is_displayed(self.issue_rgb20_button()) else None + return self.do_click(self.issue_nia_button()) if self.do_is_displayed(self.issue_nia_button()) else None diff --git a/e2e_tests/test/pageobjects/main_page_objects.py b/e2e_tests/test/pageobjects/main_page_objects.py index f06b1ad1..b42ee5e0 100644 --- a/e2e_tests/test/pageobjects/main_page_objects.py +++ b/e2e_tests/test/pageobjects/main_page_objects.py @@ -20,8 +20,8 @@ from e2e_tests.test.pageobjects.enter_wallet_password_page import EnterWalletPasswordPageObjects from e2e_tests.test.pageobjects.fungible_page import FungiblePageObjects from e2e_tests.test.pageobjects.help_page import HelpPageObjects -from e2e_tests.test.pageobjects.issue_rgb20_page import IssueRgb20PageObjects -from e2e_tests.test.pageobjects.issue_rgb25_page import IssueRgb25PageObjects +from e2e_tests.test.pageobjects.issue_cfa_page import IssueCfaPageObjects +from e2e_tests.test.pageobjects.issue_nia_page import IssueNiaPageObjects from e2e_tests.test.pageobjects.keyring_dialog_page import KeyringDialogBoxPageObjects from e2e_tests.test.pageobjects.ln_endpoint_page import LnEndpointPageObjects from e2e_tests.test.pageobjects.receive_asset_page import ReceiveAssetPageObjects @@ -88,7 +88,7 @@ def __init__(self, application): self.send_asset_page_objects = SendAssetPageObjects(self.application) - self.issue_rgb20_page_objects = IssueRgb20PageObjects(self.application) + self.issue_nia_page_objects = IssueNiaPageObjects(self.application) self.success_page_objects = SuccessPageObjects(self.application) @@ -96,7 +96,7 @@ def __init__(self, application): self.sidebar_page_objects = SidebarPageObjects(self.application) - self.issue_rgb25_page_objects = IssueRgb25PageObjects(self.application) + self.issue_cfa_page_objects = IssueCfaPageObjects(self.application) self.asset_detail_page_objects = AssetDetailPageObjects( self.application, diff --git a/e2e_tests/test/spec/test_about.py b/e2e_tests/test/spec/test_about.py index fb7b83fc..d4a67ac6 100644 --- a/e2e_tests/test/spec/test_about.py +++ b/e2e_tests/test/spec/test_about.py @@ -27,7 +27,7 @@ @allure.story('Tests for copy buttons for node info') def test_node_info(wallets_and_operations: WalletTestSetup): """Test asserting node info""" - with allure.step('Create and fund first wallet for issue rgb20'): + with allure.step('Create and fund first wallet for issue nia'): wallets_and_operations.first_page_features.wallet_features.create_and_fund_wallet( wallets_and_operations=wallets_and_operations, application=FIRST_APPLICATION, application_url=FIRST_APPLICATION_URL, fund=False, ) diff --git a/e2e_tests/test/spec/test_ask_auth_for_imp_operations.py b/e2e_tests/test/spec/test_ask_auth_for_imp_operations.py index 0554126a..6351ff5f 100644 --- a/e2e_tests/test/spec/test_ask_auth_for_imp_operations.py +++ b/e2e_tests/test/spec/test_ask_auth_for_imp_operations.py @@ -24,7 +24,7 @@ ASSET_TICKER = 'TTK' ASSET_NAME_1 = 'Tether' ASSET_AMOUNT = '2000' -ASSET_DESCRIPTION = 'RGB25 asset' +ASSET_DESCRIPTION = 'CFA asset' ASSET_NAME_2 = 'Test asset' @@ -98,38 +98,38 @@ def test_ask_auth_for_imp_question_send_bitcoin_on(wallets_and_operations: Walle @allure.story('Issuing and sending the RGB assets') -def test_ask_auth_for_imp_question_issue_rgb_20_on(wallets_and_operations: WalletTestSetup): - """Issuing RGB 20 asset with ask auth for important operations on""" - with allure.step('Issuing RGB20 asset'): +def test_ask_auth_for_imp_question_issue_nia_on(wallets_and_operations: WalletTestSetup): + """Issuing NIA asset with ask auth for important operations on""" + with allure.step('Issuing NIA asset'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_features.issue_rgb20_features.issue_rgb20_with_sufficient_sats_and_utxo( + wallets_and_operations.first_page_features.issue_nia_features.issue_nia_with_sufficient_sats_and_utxo( FIRST_APPLICATION, ASSET_TICKER, ASSET_NAME_1, ASSET_AMOUNT, is_native_auth_enabled=True, ) -@allure.story('Sending RGB20 asset') -def test_ask_auth_for_imp_question_send_rgb20_on(wallets_and_operations: WalletTestSetup): - """Sending RGB20 asset with ask auth for important operations on""" +@allure.story('Sending NIA asset') +def test_ask_auth_for_imp_question_send_nia_on(wallets_and_operations: WalletTestSetup): + """Sending NIA asset with ask auth for important operations on""" with allure.step('Getting an RGB invoice'): wallets_and_operations.second_page_operations.do_focus_on_application( SECOND_APPLICATION, ) - rgb20_invoice = wallets_and_operations.second_page_features.receive_features.receive_asset_from_sidebar( + nia_invoice = wallets_and_operations.second_page_features.receive_features.receive_asset_from_sidebar( SECOND_APPLICATION, ) - with allure.step('Sending the RGB20 asset'): + with allure.step('Sending the NIA asset'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( ASSET_AMOUNT, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_send_button() wallets_and_operations.first_page_features.send_features.send( - FIRST_APPLICATION, rgb20_invoice, ASSET_AMOUNT, is_native_auth_enabled=True, + FIRST_APPLICATION, nia_invoice, ASSET_AMOUNT, is_native_auth_enabled=True, ) _, toaster_description = wallets_and_operations.first_page_objects.toaster_page_objects.click_toaster_frame() with allure.step('asserting tx id'): @@ -137,7 +137,7 @@ def test_ask_auth_for_imp_question_send_rgb20_on(wallets_and_operations: WalletT SECOND_APPLICATION, ) wallets_and_operations.second_page_objects.fungible_page_objects.click_refresh_button() - wallets_and_operations.second_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.second_page_objects.fungible_page_objects.click_nia_frame( ASSET_NAME_1, ) wallets_and_operations.second_page_objects.asset_detail_page_objects.click_rgb_transaction_on_chain_frame() @@ -148,40 +148,40 @@ def test_ask_auth_for_imp_question_send_rgb20_on(wallets_and_operations: WalletT assert toaster_description == INFO_ASSET_SENT.format(tx_id) -@allure.story('Issuing RGB25 asset') -def test_ask_auth_for_imp_question_issue_rgb_25_on(wallets_and_operations: WalletTestSetup): - """Issuing RGB25 asset with ask auth for important operations on""" - with allure.step('Issuing RGB25 asset'): +@allure.story('Issuing CFA asset') +def test_ask_auth_for_imp_question_issue_cfa_on(wallets_and_operations: WalletTestSetup): + """Issuing CFA asset with ask auth for important operations on""" + with allure.step('Issuing CFA asset'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_features.issue_rgb25_features.issue_rgb25_with_sufficient_sats_and_utxo( + wallets_and_operations.first_page_features.issue_cfa_features.issue_cfa_with_sufficient_sats_and_utxo( FIRST_APPLICATION, ASSET_NAME_1, ASSET_DESCRIPTION, ASSET_AMOUNT, is_native_auth_enabled=True, ) -@allure.story('Sending RGB25 asset') -def test_ask_auth_for_imp_question_send_rgb_25_on(wallets_and_operations: WalletTestSetup): - """Sending RGB25 asset with ask auth for important operations on""" +@allure.story('Sending CFA asset') +def test_ask_auth_for_imp_question_send_cfa_on(wallets_and_operations: WalletTestSetup): + """Sending CFA asset with ask auth for important operations on""" with allure.step('Getting an RGB invoice'): wallets_and_operations.second_page_operations.do_focus_on_application( SECOND_APPLICATION, ) - rgb25_invoice = wallets_and_operations.second_page_features.receive_features.receive_asset_from_sidebar( + cfa_invoice = wallets_and_operations.second_page_features.receive_features.receive_asset_from_sidebar( SECOND_APPLICATION, ) - with allure.step('Sending the RGB25 asset'): + with allure.step('Sending the CFA asset'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) wallets_and_operations.first_page_objects.sidebar_page_objects.click_collectibles_button() - wallets_and_operations.first_page_objects.collectible_page_objects.click_rgb25_frame( + wallets_and_operations.first_page_objects.collectible_page_objects.click_cfa_frame( ASSET_NAME_1, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_send_button() wallets_and_operations.first_page_features.send_features.send( - FIRST_APPLICATION, rgb25_invoice, ASSET_AMOUNT, is_native_auth_enabled=True, + FIRST_APPLICATION, cfa_invoice, ASSET_AMOUNT, is_native_auth_enabled=True, ) _, toaster_description = wallets_and_operations.first_page_objects.toaster_page_objects.click_toaster_frame() with allure.step('asserting tx id'): @@ -190,7 +190,7 @@ def test_ask_auth_for_imp_question_send_rgb_25_on(wallets_and_operations: Wallet ) wallets_and_operations.second_page_objects.sidebar_page_objects.click_collectibles_button() wallets_and_operations.second_page_objects.collectible_page_objects.click_refresh_button() - wallets_and_operations.second_page_objects.collectible_page_objects.click_rgb25_frame( + wallets_and_operations.second_page_objects.collectible_page_objects.click_cfa_frame( ASSET_NAME_1, ) wallets_and_operations.second_page_objects.asset_detail_page_objects.click_rgb_transaction_on_chain_frame() @@ -255,39 +255,39 @@ def test_ask_auth_for_imp_question_send_bitcoin_off(wallets_and_operations: Wall assert toaster_title == 'Success' -@allure.story('Issuing RGB20 asset') -def test_ask_auth_for_imp_question_issue_rgb_20_off(wallets_and_operations: WalletTestSetup): - """Issuing RGB20 asset with ask auth for important operations off""" - with allure.step('Issuing RGB20 asset'): +@allure.story('Issuing NIA asset') +def test_ask_auth_for_imp_question_issue_nia_off(wallets_and_operations: WalletTestSetup): + """Issuing NIA asset with ask auth for important operations off""" + with allure.step('Issuing NIA asset'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_features.issue_rgb20_features.issue_rgb20_with_sufficient_sats_and_utxo( + wallets_and_operations.first_page_features.issue_nia_features.issue_nia_with_sufficient_sats_and_utxo( FIRST_APPLICATION, ASSET_TICKER, ASSET_NAME_2, ASSET_AMOUNT, ) -@allure.story('Sending RGB20 asset') -def test_ask_auth_for_imp_question_send_rgb_20_off(wallets_and_operations: WalletTestSetup): - """Sending RGB20 asset with ask auth for important operations off""" +@allure.story('Sending NIA asset') +def test_ask_auth_for_imp_question_send_nia_off(wallets_and_operations: WalletTestSetup): + """Sending NIA asset with ask auth for important operations off""" with allure.step('Getting an RGB invoice'): wallets_and_operations.second_page_operations.do_focus_on_application( SECOND_APPLICATION, ) - rgb20_invoice = wallets_and_operations.second_page_features.receive_features.receive_asset_from_sidebar( + nia_invoice = wallets_and_operations.second_page_features.receive_features.receive_asset_from_sidebar( SECOND_APPLICATION, ) - with allure.step('Sending the RGB20 asset'): + with allure.step('Sending the NIA asset'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( ASSET_NAME_2, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_send_button() wallets_and_operations.first_page_features.send_features.send( - FIRST_APPLICATION, rgb20_invoice, ASSET_AMOUNT, + FIRST_APPLICATION, nia_invoice, ASSET_AMOUNT, ) _, toaster_description = wallets_and_operations.first_page_objects.toaster_page_objects.click_toaster_frame() wallets_and_operations.first_page_objects.fungible_page_objects.click_refresh_button() @@ -296,7 +296,7 @@ def test_ask_auth_for_imp_question_send_rgb_20_off(wallets_and_operations: Walle SECOND_APPLICATION, ) wallets_and_operations.second_page_objects.fungible_page_objects.click_refresh_button() - wallets_and_operations.second_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.second_page_objects.fungible_page_objects.click_nia_frame( ASSET_NAME_2, ) wallets_and_operations.second_page_objects.asset_detail_page_objects.click_rgb_transaction_on_chain_frame() @@ -307,41 +307,41 @@ def test_ask_auth_for_imp_question_send_rgb_20_off(wallets_and_operations: Walle assert toaster_description == INFO_ASSET_SENT.format(tx_id) -@allure.story('Issuing RGB25 asset') -def test_ask_auth_for_imp_question_issue_rgb_25_off(wallets_and_operations: WalletTestSetup): - """Issuing RGB20 asset with ask auth for important operations off""" - with allure.step('Issuing RGB25 asset'): +@allure.story('Issuing CFA asset') +def test_ask_auth_for_imp_question_issue_cfa_off(wallets_and_operations: WalletTestSetup): + """Issuing NIA asset with ask auth for important operations off""" + with allure.step('Issuing CFA asset'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) wallets_and_operations.first_page_objects.fungible_page_objects.click_refresh_button() - wallets_and_operations.first_page_features.issue_rgb25_features.issue_rgb25_with_sufficient_sats_and_utxo( + wallets_and_operations.first_page_features.issue_cfa_features.issue_cfa_with_sufficient_sats_and_utxo( FIRST_APPLICATION, ASSET_NAME_2, ASSET_DESCRIPTION, ASSET_AMOUNT, ) -@allure.story('Sending RGB25 asset') -def test_ask_auth_for_imp_question_send_rgb_25_off(wallets_and_operations: WalletTestSetup): - """Sending RGB25 asset with ask auth for important operations off""" +@allure.story('Sending CFA asset') +def test_ask_auth_for_imp_question_send_cfa_off(wallets_and_operations: WalletTestSetup): + """Sending CFA asset with ask auth for important operations off""" with allure.step('Getting an RGB invoice'): wallets_and_operations.second_page_operations.do_focus_on_application( SECOND_APPLICATION, ) - rgb25_invoice = wallets_and_operations.second_page_features.receive_features.receive_asset_from_sidebar( + cfa_invoice = wallets_and_operations.second_page_features.receive_features.receive_asset_from_sidebar( SECOND_APPLICATION, ) - with allure.step('Sending the RGB25 asset'): + with allure.step('Sending the CFA asset'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) wallets_and_operations.first_page_objects.collectible_page_objects.click_refresh_button() - wallets_and_operations.first_page_objects.collectible_page_objects.click_rgb25_frame( + wallets_and_operations.first_page_objects.collectible_page_objects.click_cfa_frame( ASSET_NAME_2, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_send_button() wallets_and_operations.first_page_features.send_features.send( - FIRST_APPLICATION, rgb25_invoice, ASSET_AMOUNT, + FIRST_APPLICATION, cfa_invoice, ASSET_AMOUNT, ) _, toaster_description = wallets_and_operations.first_page_objects.toaster_page_objects.click_toaster_frame() with allure.step('asserting tx id'): @@ -350,7 +350,7 @@ def test_ask_auth_for_imp_question_send_rgb_25_off(wallets_and_operations: Walle ) wallets_and_operations.second_page_objects.sidebar_page_objects.click_collectibles_button() wallets_and_operations.second_page_objects.collectible_page_objects.click_refresh_button() - wallets_and_operations.second_page_objects.collectible_page_objects.click_rgb25_frame( + wallets_and_operations.second_page_objects.collectible_page_objects.click_cfa_frame( ASSET_NAME_2, ) wallets_and_operations.second_page_objects.asset_detail_page_objects.click_rgb_transaction_on_chain_frame() diff --git a/e2e_tests/test/spec/test_backup_and_restore.py b/e2e_tests/test/spec/test_backup_and_restore.py index e36f6e6e..25425d62 100644 --- a/e2e_tests/test/spec/test_backup_and_restore.py +++ b/e2e_tests/test/spec/test_backup_and_restore.py @@ -9,6 +9,7 @@ from dotenv import load_dotenv from accessible_constant import FIRST_APPLICATION +from accessible_constant import SECOND_APPLICATION from e2e_tests.test.utilities.app_setup import load_qm_translation from e2e_tests.test.utilities.app_setup import test_environment from e2e_tests.test.utilities.app_setup import wallets_and_operations @@ -21,12 +22,22 @@ BACKUP_EMAIL_PASSWORD = os.getenv('BACKUP_EMAIL_PASSWORD') MNEMONIC = None PASSWORD = None +NIA_ASSET_ID = None +CFA_ASSET_ID = None pytestmark = pytest.mark.order(1) +NIA_TICKER = 'N20' +NIA_NAME = 'NIA_Asset_A' +NIA_TOTAL = '200' +NIA_SEND_AMOUNT = '20' + +CFA_NAME = 'CFA_Asset' +CFA_DESC = 'CFA Asset' +CFA_TOTAL = '300' +CFA_SEND_AMOUNT = '25' @pytest.mark.skip_for_remote -@pytest.mark.parametrize('test_environment', [False], indirect=True) @allure.feature('Mnemonic and backup configuration') @allure.story('Mnemonic and backup configuration functionality') def test_mnemonic_and_backup_configure(wallets_and_operations: WalletTestSetup, load_qm_translation): @@ -43,10 +54,19 @@ def test_mnemonic_and_backup_configure(wallets_and_operations: WalletTestSetup, global MNEMONIC global PASSWORD with allure.step('Create a embedded wallet'): - wallets_and_operations.first_page_features.wallet_features.create_embedded_wallet( + wallets_and_operations.first_page_features.wallet_features.create_and_fund_wallet( + wallets_and_operations, FIRST_APPLICATION, ) + wallets_and_operations.second_page_features.wallet_features.create_and_fund_wallet( + wallets_and_operations, + SECOND_APPLICATION, + ) + with allure.step('Check the backup configuration'): + wallets_and_operations.first_page_operations.do_focus_on_application( + FIRST_APPLICATION, + ) backup_tooltip = wallets_and_operations.first_page_objects.fungible_page_objects.get_backup_tooltip() assert backup_tooltip == TranslationManager.translate( 'backup_tooltip_text', @@ -70,10 +90,88 @@ def test_mnemonic_and_backup_configure(wallets_and_operations: WalletTestSetup, wallets_and_operations.first_page_objects.sidebar_page_objects.click_fungibles_button() +@allure.feature('Backup/Restore with cross-wallet transfers') +@allure.story('Issue NIA/CFA, send between wallets, send BTC, backup and restore wallet A, then assert state') +@pytest.mark.skip_for_remote +def test_transfer_btc_and_asset(wallets_and_operations: WalletTestSetup, load_qm_translation, test_environment): + """ + Scenario: + - Launch two apps (A and B) and fund both wallets + - In wallet A: issue NIA and send to wallet B (amount 20) + - In wallet B: issue CFA and send to wallet A (amount 25) + - Send BTC from A to B + - Backup wallet A, restart env, restore wallet A + - Assert wallet A shows received CFA and sent NIA, and BTC history reflects send + """ + global NIA_ASSET_ID, CFA_ASSET_ID + with allure.step('Wallet A issues NIA'): + wallets_and_operations.first_page_operations.do_focus_on_application( + FIRST_APPLICATION, + ) + wallets_and_operations.first_page_features.issue_nia_features.issue_nia_with_sufficient_sats_and_utxo( + application=FIRST_APPLICATION, asset_ticker=NIA_TICKER, asset_name=NIA_NAME, asset_amount=NIA_TOTAL, + ) + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( + NIA_NAME, + ) + wallets_and_operations.first_page_objects.asset_detail_page_objects.click_copy_button() + NIA_ASSET_ID = wallets_and_operations.first_page_operations.do_get_copied_address() + wallets_and_operations.first_page_objects.asset_detail_page_objects.click_close_button() + + with allure.step('Wallet B generates RGB receive invoice for NIA'): + wallets_and_operations.second_page_operations.do_focus_on_application( + SECOND_APPLICATION, + ) + invoice_b = wallets_and_operations.second_page_features.receive_features.receive_asset_from_sidebar( + SECOND_APPLICATION, + ) + + with allure.step('Wallet A sends NIA to wallet B'): + wallets_and_operations.first_page_operations.do_focus_on_application( + FIRST_APPLICATION, + ) + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( + NIA_NAME, + ) + wallets_and_operations.first_page_objects.asset_detail_page_objects.click_send_button() + wallets_and_operations.first_page_features.send_features.send( + application=FIRST_APPLICATION, receiver_invoice=invoice_b, amount=NIA_SEND_AMOUNT, + ) + + with allure.step('Wallet B issues CFA'): + wallets_and_operations.second_page_features.issue_cfa_features.issue_cfa_with_sufficient_sats_and_utxo( + application=SECOND_APPLICATION, asset_name=CFA_NAME, asset_description=CFA_DESC, asset_amount=CFA_TOTAL, + ) + wallets_and_operations.second_page_objects.sidebar_page_objects.click_collectibles_button() + wallets_and_operations.second_page_objects.collectible_page_objects.click_cfa_frame( + CFA_NAME, + ) + wallets_and_operations.second_page_objects.asset_detail_page_objects.click_copy_button() + CFA_ASSET_ID = wallets_and_operations.second_page_operations.do_get_copied_address() + wallets_and_operations.second_page_objects.asset_detail_page_objects.click_close_button() + + with allure.step('Wallet A generates RGB receive invoice for CFA'): + invoice_a = wallets_and_operations.first_page_features.receive_features.receive_asset_from_sidebar( + FIRST_APPLICATION, + ) + + with allure.step('Wallet B sends CFA to wallet A'): + wallets_and_operations.second_page_operations.do_focus_on_application( + SECOND_APPLICATION, + ) + wallets_and_operations.second_page_objects.sidebar_page_objects.click_collectibles_button() + wallets_and_operations.second_page_objects.collectible_page_objects.click_cfa_frame( + CFA_NAME, + ) + wallets_and_operations.second_page_objects.asset_detail_page_objects.click_send_button() + wallets_and_operations.second_page_features.send_features.send( + application=SECOND_APPLICATION, receiver_invoice=invoice_a, amount=CFA_SEND_AMOUNT, + ) + + @allure.feature('Backup page') @allure.story('Backup page functionality') @pytest.mark.skip_for_remote -@pytest.mark.parametrize('test_environment', [False], indirect=True) def test_backup(test_environment, wallets_and_operations: WalletTestSetup): """ Test the backup page functionality. @@ -84,6 +182,9 @@ def test_backup(test_environment, wallets_and_operations: WalletTestSetup): """ description = None with allure.step('Configure backup'): + wallets_and_operations.first_page_operations.do_focus_on_application( + FIRST_APPLICATION, + ) wallets_and_operations.first_page_objects.sidebar_page_objects.click_backup_button() wallets_and_operations.first_page_objects.backup_page_objects.click_configurable_button() wallets_and_operations.first_page_objects.backup_page_objects.click_backup_window() @@ -118,13 +219,15 @@ def test_backup(test_environment, wallets_and_operations: WalletTestSetup): @allure.feature('Restore page') @allure.story('Restore page functionality') @pytest.mark.skip_for_remote -@pytest.mark.parametrize('test_environment', [False], indirect=True) def test_restore(wallets_and_operations: WalletTestSetup): """ This test case is used to restore the wallet from the backup. """ description = None with allure.step('Restore the wallet'): + wallets_and_operations.first_page_operations.do_focus_on_application( + FIRST_APPLICATION, + ) wallets_and_operations.first_page_objects.term_and_condition_page_objects.scroll_to_end() wallets_and_operations.first_page_objects.term_and_condition_page_objects.click_accept_button() wallets_and_operations.first_page_objects.wallet_selection_page_objects.click_embedded_button() @@ -160,3 +263,42 @@ def test_restore(wallets_and_operations: WalletTestSetup): wallets_and_operations.first_page_operations.wait_for_toaster_message() _, description = wallets_and_operations.first_page_objects.toaster_page_objects.click_toaster_frame() assert description == INFO_RESTORE_COMPLETED + wallets_and_operations.first_page_objects.enter_wallet_password_page_objects.enter_password( + PASSWORD, + ) + wallets_and_operations.first_page_objects.enter_wallet_password_page_objects.click_login_button() + + +@allure.feature('Restore page') +@allure.story('Restore page functionality') +@pytest.mark.skip_for_remote +def test_restore_asset(wallets_and_operations: WalletTestSetup): + """ + This test case is used to restore the wallet from the backup. + """ + with allure.step('assert the wallet assets'): + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( + NIA_NAME, + ) + wallets_and_operations.first_page_objects.asset_detail_page_objects.click_copy_button() + nia_asset_id = wallets_and_operations.first_page_operations.do_get_copied_address() + wallets_and_operations.first_page_objects.asset_detail_page_objects.click_rgb_transaction_on_chain_frame() + nia_transferred_amount = wallets_and_operations.first_page_objects.asset_transaction_detail_page_objects.get_transferred_amount() + wallets_and_operations.first_page_objects.asset_transaction_detail_page_objects.click_close_button() + wallets_and_operations.first_page_objects.asset_detail_page_objects.click_close_button() + wallets_and_operations.first_page_objects.sidebar_page_objects.click_collectibles_button() + wallets_and_operations.first_page_objects.collectible_page_objects.click_cfa_frame( + CFA_NAME, + ) + wallets_and_operations.first_page_objects.asset_detail_page_objects.click_copy_button() + cfa_asset_id = wallets_and_operations.first_page_operations.do_get_copied_address() + wallets_and_operations.first_page_objects.asset_detail_page_objects.click_rgb_transaction_on_chain_frame() + cfa_transferred_amount = wallets_and_operations.first_page_objects.asset_transaction_detail_page_objects.get_transferred_amount() + wallets_and_operations.first_page_objects.asset_transaction_detail_page_objects.click_close_button() + wallets_and_operations.first_page_objects.asset_detail_page_objects.click_close_button() + wallets_and_operations.first_page_objects.sidebar_page_objects.click_collectibles_button() + + assert nia_asset_id == NIA_ASSET_ID + assert cfa_asset_id == CFA_ASSET_ID + assert nia_transferred_amount == f'-{NIA_SEND_AMOUNT}' + assert cfa_transferred_amount == f'+{CFA_SEND_AMOUNT}' diff --git a/e2e_tests/test/spec/test_create_and_close_channel.py b/e2e_tests/test/spec/test_create_and_close_channel.py index c349b5f8..1e1f155f 100644 --- a/e2e_tests/test/spec/test_create_and_close_channel.py +++ b/e2e_tests/test/spec/test_create_and_close_channel.py @@ -43,14 +43,14 @@ def test_create_channel_with_actual_value(wallets_and_operations: WalletTestSetu wallets_and_operations=wallets_and_operations, application=SECOND_APPLICATION, application_url=SECOND_APPLICATION_URL, ) - with allure.step('Issue RGB20 asset for rgb20 channel creation'): + with allure.step('Issue NIA asset for nia channel creation'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_features.issue_rgb20_features.issue_rgb20_with_sufficient_sats_and_utxo( + wallets_and_operations.first_page_features.issue_nia_features.issue_nia_with_sufficient_sats_and_utxo( application=FIRST_APPLICATION, asset_ticker=ASSET_TICKER, asset_name=ASSET_NAME, asset_amount=ASSET_AMOUNT, ) - wallets_and_operations.first_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( asset_name=ASSET_NAME, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_copy_button() diff --git a/e2e_tests/test/spec/test_fail_transfer.py b/e2e_tests/test/spec/test_fail_transfer.py index 47f17efc..edadc932 100644 --- a/e2e_tests/test/spec/test_fail_transfer.py +++ b/e2e_tests/test/spec/test_fail_transfer.py @@ -14,7 +14,7 @@ ASSET_TICKER = 'TTK' -RGB20_ASSET_NAME = 'Tether' +NIA_ASSET_NAME = 'Tether' ASSET_AMOUNT = '2000' @@ -30,13 +30,13 @@ def test_fail_transfer(wallets_and_operations: WalletTestSetup): ) with allure.step('Issuing an RGB asset'): - wallets_and_operations.first_page_features.issue_rgb20_features.issue_rgb20_with_sufficient_sats_and_utxo( - FIRST_APPLICATION, ASSET_TICKER, RGB20_ASSET_NAME, ASSET_AMOUNT, + wallets_and_operations.first_page_features.issue_nia_features.issue_nia_with_sufficient_sats_and_utxo( + FIRST_APPLICATION, ASSET_TICKER, NIA_ASSET_NAME, ASSET_AMOUNT, ) with allure.step('Generating an invoice'): - wallets_and_operations.first_page_objects.fungible_page_objects.click_rgb20_frame( - asset_name=RGB20_ASSET_NAME, + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( + asset_name=NIA_ASSET_NAME, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_receive_button() wallets_and_operations.first_page_features.receive_features.receive( @@ -44,8 +44,8 @@ def test_fail_transfer(wallets_and_operations: WalletTestSetup): ) with allure.step('Failing the transfer'): - wallets_and_operations.first_page_objects.fungible_page_objects.click_rgb20_frame( - asset_name=RGB20_ASSET_NAME, + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( + asset_name=NIA_ASSET_NAME, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_fail_transfer_button() wallets_and_operations.first_page_objects.asset_detail_page_objects.click_confirmation_continue_button() diff --git a/e2e_tests/test/spec/test_hide_exhausted_asset.py b/e2e_tests/test/spec/test_hide_exhausted_asset.py index 604ba894..72cd7b8a 100644 --- a/e2e_tests/test/spec/test_hide_exhausted_asset.py +++ b/e2e_tests/test/spec/test_hide_exhausted_asset.py @@ -15,7 +15,7 @@ ASSET_TICKER = 'TTK' ASSET_NAME = 'Tether' ASSET_AMOUNT = '2000' -ISSUE_RGB20_TOASTER_MESSAGE = 'You have insufficient funds' +ISSUE_NIA_TOASTER_MESSAGE = 'You have insufficient funds' @allure.feature('Hide exhausted asset') @@ -46,7 +46,7 @@ def test_hide_exhausted_asset_on(wallets_and_operations: WalletTestSetup): with allure.step('Issuing a asset'): wallets_and_operations.first_page_objects.sidebar_page_objects.click_fungibles_button() - wallets_and_operations.first_page_features.issue_rgb20_features.issue_rgb20_with_sufficient_sats_and_utxo( + wallets_and_operations.first_page_features.issue_nia_features.issue_nia_with_sufficient_sats_and_utxo( FIRST_APPLICATION, ASSET_TICKER, ASSET_NAME, ASSET_AMOUNT, ) @@ -63,7 +63,7 @@ def test_hide_exhausted_asset_on(wallets_and_operations: WalletTestSetup): FIRST_APPLICATION, ) wallets_and_operations.first_page_objects.fungible_page_objects.click_refresh_button() - wallets_and_operations.first_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( ASSET_NAME, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_send_button() diff --git a/e2e_tests/test/spec/test_issue_cfa_asset.py b/e2e_tests/test/spec/test_issue_cfa_asset.py new file mode 100644 index 00000000..73d3b710 --- /dev/null +++ b/e2e_tests/test/spec/test_issue_cfa_asset.py @@ -0,0 +1,105 @@ +# pylint: disable=redefined-outer-name, unused-import +""" +Tests for issuing cfa assets with different scenarios. +""" +from __future__ import annotations + +import allure +import pytest + +from accessible_constant import FIRST_APPLICATION +from accessible_constant import FIRST_APPLICATION_URL +from e2e_tests.test.utilities.app_setup import test_environment +from e2e_tests.test.utilities.app_setup import wallets_and_operations +from e2e_tests.test.utilities.app_setup import WalletTestSetup + +CFA_ASSET_NAME = 'Cfa' +ASSET_DESCRIPTION = 'This is Cfa asset' +ASSET_AMOUNT = '2000' +ISSUE_CFA_TOASTER_MESSAGE = 'You have insufficient funds' + + +@pytest.mark.parametrize('test_environment', [False], indirect=True) +@allure.feature('Issue cfa asset without sufficient sats') +@allure.story('Issue cfa asset without sufficient sats which will produce error toaster') +def test_issue_cfa_without_sufficient_sats(wallets_and_operations: WalletTestSetup): + """ + Test issuing cfa asset without sufficient sats. + """ + + with allure.step('Create and fund first wallet for issue cfa'): + wallets_and_operations.first_page_features.wallet_features.create_and_fund_wallet( + wallets_and_operations=wallets_and_operations, application=FIRST_APPLICATION, application_url=FIRST_APPLICATION_URL, fund=False, + ) + + with allure.step('Issue cfa asset without sat'): + description = wallets_and_operations.first_page_features.issue_cfa_features.issue_cfa_asset_without_sat( + FIRST_APPLICATION, CFA_ASSET_NAME, ASSET_DESCRIPTION, ASSET_AMOUNT, + ) + + with allure.step('Verify toaster title and message'): + assert description == ISSUE_CFA_TOASTER_MESSAGE + + +@pytest.mark.parametrize('test_environment', [False], indirect=True) +@allure.feature('Issue cfa asset with sufficient sats but no utxo') +@allure.story('Issue cfa asset with sufficient sats and no utxo which will first create utxo and then create asset') +def test_issue_cfa_with_sufficient_sats_and_no_utxo(wallets_and_operations: WalletTestSetup): + """ + Test issuing cfa asset with sufficient sats but no utxo. + """ + + with allure.step('Fund wallet for issue cfa asset'): + wallets_and_operations.first_page_features.wallet_features.fund_wallet( + FIRST_APPLICATION, + ) + + with allure.step('Verifies there is no utxo for issue cfa asset'): + wallets_and_operations.first_page_objects.sidebar_page_objects.click_view_unspents_button() + count = wallets_and_operations.first_page_objects.view_unspent_list_page_objects.get_unspent_widget() + wallets_and_operations.first_page_objects.sidebar_page_objects.click_fungibles_button() + assert count == 1 + + with allure.step('Issue cfa with sufficient sats and no utxo'): + wallets_and_operations.first_page_features.issue_cfa_features.issue_cfa_with_sufficient_sats_and_no_utxo( + FIRST_APPLICATION, CFA_ASSET_NAME, ASSET_DESCRIPTION, ASSET_AMOUNT, + ) + + with allure.step('Verify asset name'): + asset_name = wallets_and_operations.first_page_objects.collectible_page_objects.get_cfa_asset_name( + CFA_ASSET_NAME, + ) + assert asset_name == CFA_ASSET_NAME + + +@pytest.mark.parametrize('test_environment', [False], indirect=True) +@allure.feature('Issue cfa asset with sufficient sats and utxo') +@allure.story('Issue cfa asset with sufficient sats and utxo which will create asset') +def test_issue_cfa_with_sufficient_sats_and_utxo(wallets_and_operations: WalletTestSetup): + """ + Test issuing cfa asset with sufficient sats and utxo. + """ + + with allure.step('Verified that one utxo exists for issue cfa asset'): + wallets_and_operations.first_page_objects.sidebar_page_objects.click_view_unspents_button() + + count = wallets_and_operations.first_page_objects.view_unspent_list_page_objects.get_unspent_widget() + + asset_id = wallets_and_operations.first_page_objects.view_unspent_list_page_objects.get_unspent_utxo_asset_id( + 'NA', + ) + wallets_and_operations.first_page_objects.sidebar_page_objects.click_fungibles_button() + + assert count == 3 + assert asset_id == 'NA' + + with allure.step('Issue cfa with sufficient sats and utxo'): + wallets_and_operations.first_page_features.issue_cfa_features.issue_cfa_with_sufficient_sats_and_utxo( + FIRST_APPLICATION, CFA_ASSET_NAME, ASSET_DESCRIPTION, ASSET_AMOUNT, + ) + + with allure.step('Verify asset name'): + asset_name = wallets_and_operations.first_page_objects.collectible_page_objects.get_cfa_asset_name( + CFA_ASSET_NAME, + ) + assert asset_name == CFA_ASSET_NAME diff --git a/e2e_tests/test/spec/test_issue_nia_asset.py b/e2e_tests/test/spec/test_issue_nia_asset.py new file mode 100644 index 00000000..1884ad08 --- /dev/null +++ b/e2e_tests/test/spec/test_issue_nia_asset.py @@ -0,0 +1,101 @@ +# pylint: disable=redefined-outer-name, unused-import +""" +Tests for nia asset issuance. +""" +from __future__ import annotations + +import allure +import pytest + +from accessible_constant import FIRST_APPLICATION +from accessible_constant import FIRST_APPLICATION_URL +from e2e_tests.test.utilities.app_setup import test_environment +from e2e_tests.test.utilities.app_setup import wallets_and_operations +from e2e_tests.test.utilities.app_setup import WalletTestSetup + +ASSET_TICKER = 'TTK' +NIA_ASSET_NAME = 'Tether' +ASSET_AMOUNT = '2000' +ISSUE_NIA_TOASTER_MESSAGE = 'You have insufficient funds' + + +@pytest.mark.parametrize('test_environment', [False], indirect=True) +@allure.feature('Issue nia asset without sufficient sats') +@allure.story('Issue nia asset without sufficient sats which will produce error toaster') +def test_issue_nia_without_sufficient_sats(wallets_and_operations: WalletTestSetup): + """ + Test nia asset issuance without sufficient sats. + """ + + with allure.step('Create and fund first wallet for issue nia'): + wallets_and_operations.first_page_features.wallet_features.create_and_fund_wallet( + wallets_and_operations=wallets_and_operations, application=FIRST_APPLICATION, application_url=FIRST_APPLICATION_URL, fund=False, + ) + + with allure.step('Issue nia asset without sufficient sats'): + description = wallets_and_operations.first_page_features.issue_nia_features.issue_nia_asset_without_sat( + FIRST_APPLICATION, ASSET_TICKER, NIA_ASSET_NAME, ASSET_AMOUNT, + ) + + assert description == ISSUE_NIA_TOASTER_MESSAGE + + +@pytest.mark.parametrize('test_environment', [False], indirect=True) +@allure.feature('Issue nia asset with sufficient sats and no utxo') +@allure.story('Issue nia asset with sufficient sats which will create utxo and create asset') +def test_issue_nia_with_sufficient_sats_and_no_utxo(wallets_and_operations: WalletTestSetup): + """ + Test nia asset issuance with sufficient sats and no utxo. + """ + + with allure.step('Fund wallet for issue nia asset'): + wallets_and_operations.first_page_features.wallet_features.fund_wallet( + FIRST_APPLICATION, + ) + + with allure.step('Verifies there is no utxo for issue nia asset'): + wallets_and_operations.first_page_objects.sidebar_page_objects.click_view_unspents_button() + count = wallets_and_operations.first_page_objects.view_unspent_list_page_objects.get_unspent_widget() + wallets_and_operations.first_page_objects.sidebar_page_objects.click_fungibles_button() + assert count == 1 + + with allure.step('Issue nia asset with sufficient sats and no utxo'): + wallets_and_operations.first_page_features.issue_nia_features.issue_nia_with_sufficient_sats_and_no_utxo( + FIRST_APPLICATION, ASSET_TICKER, NIA_ASSET_NAME, ASSET_AMOUNT, + ) + + with allure.step('Verify asset name'): + asset_name = wallets_and_operations.first_page_objects.fungible_page_objects.get_nia_asset_name( + NIA_ASSET_NAME, + ) + assert asset_name == NIA_ASSET_NAME + + +@pytest.mark.parametrize('test_environment', [False], indirect=True) +@allure.feature('Issue nia asset with sufficient sats') +@allure.story('Issue nia asset with sufficient sats which will create asset') +def test_issue_nia_with_sufficient_sats_and_utxo(wallets_and_operations: WalletTestSetup): + """ + Test nia asset issuance with sufficient sats and utxo. + """ + + with allure.step('Verified that one utxo exists for issue nia asset'): + wallets_and_operations.first_page_objects.sidebar_page_objects.click_view_unspents_button() + count = wallets_and_operations.first_page_objects.view_unspent_list_page_objects.get_unspent_widget() + nia_asset_id = wallets_and_operations.first_page_objects.view_unspent_list_page_objects.get_unspent_utxo_asset_id( + 'NA', + ) + wallets_and_operations.first_page_objects.sidebar_page_objects.click_fungibles_button() + assert count == 3 + assert nia_asset_id == 'NA' + + with allure.step('Issue nia asset with sufficient sats and utxo'): + wallets_and_operations.first_page_features.issue_nia_features.issue_nia_with_sufficient_sats_and_utxo( + FIRST_APPLICATION, ASSET_TICKER, NIA_ASSET_NAME, ASSET_AMOUNT, + ) + + with allure.step('Verify asset name'): + asset_name = wallets_and_operations.first_page_objects.fungible_page_objects.get_nia_asset_name( + NIA_ASSET_NAME, + ) + assert asset_name == NIA_ASSET_NAME diff --git a/e2e_tests/test/spec/test_issue_rgb20_asset.py b/e2e_tests/test/spec/test_issue_rgb20_asset.py deleted file mode 100644 index a93969ef..00000000 --- a/e2e_tests/test/spec/test_issue_rgb20_asset.py +++ /dev/null @@ -1,102 +0,0 @@ -# pylint: disable=redefined-outer-name, unused-import -""" -Tests for rgb20 asset issuance. -""" -from __future__ import annotations - -import allure -import pytest - -from accessible_constant import FIRST_APPLICATION -from accessible_constant import FIRST_APPLICATION_URL -from e2e_tests.test.utilities.app_setup import test_environment -from e2e_tests.test.utilities.app_setup import wallets_and_operations -from e2e_tests.test.utilities.app_setup import WalletTestSetup -from src.model.enums.enums_model import WalletType - -ASSET_TICKER = 'TTK' -RGB20_ASSET_NAME = 'Tether' -ASSET_AMOUNT = '2000' -ISSUE_RGB20_TOASTER_MESSAGE = 'You have insufficient funds' - - -@pytest.mark.parametrize('test_environment', [False], indirect=True) -@allure.feature('Issue rgb20 asset without sufficient sats') -@allure.story('Issue rgb20 asset without sufficient sats which will produce error toaster') -def test_issue_rgb20_without_sufficient_sats(wallets_and_operations: WalletTestSetup): - """ - Test rgb20 asset issuance without sufficient sats. - """ - - with allure.step('Create and fund first wallet for issue rgb20'): - wallets_and_operations.first_page_features.wallet_features.create_and_fund_wallet( - wallets_and_operations=wallets_and_operations, application=FIRST_APPLICATION, application_url=FIRST_APPLICATION_URL, fund=False, - ) - - with allure.step('Issue rgb20 asset without sufficient sats'): - description = wallets_and_operations.first_page_features.issue_rgb20_features.issue_rgb20_asset_without_sat( - FIRST_APPLICATION, ASSET_TICKER, RGB20_ASSET_NAME, ASSET_AMOUNT, - ) - - assert description == ISSUE_RGB20_TOASTER_MESSAGE - - -@pytest.mark.parametrize('test_environment', [False], indirect=True) -@allure.feature('Issue rgb20 asset with sufficient sats and no utxo') -@allure.story('Issue rgb20 asset with sufficient sats which will create utxo and create asset') -def test_issue_rgb20_with_sufficient_sats_and_no_utxo(wallets_and_operations: WalletTestSetup): - """ - Test rgb20 asset issuance with sufficient sats and no utxo. - """ - - with allure.step('Fund wallet for issue rgb20 asset'): - wallets_and_operations.first_page_features.wallet_features.fund_wallet( - FIRST_APPLICATION, - ) - - with allure.step('Verifies there is no utxo for issue rgb20 asset'): - wallets_and_operations.first_page_objects.sidebar_page_objects.click_view_unspents_button() - count = wallets_and_operations.first_page_objects.view_unspent_list_page_objects.get_unspent_widget() - wallets_and_operations.first_page_objects.sidebar_page_objects.click_fungibles_button() - assert count == 1 - - with allure.step('Issue rgb20 asset with sufficient sats and no utxo'): - wallets_and_operations.first_page_features.issue_rgb20_features.issue_rgb20_with_sufficient_sats_and_no_utxo( - FIRST_APPLICATION, ASSET_TICKER, RGB20_ASSET_NAME, ASSET_AMOUNT, - ) - - with allure.step('Verify asset name'): - asset_name = wallets_and_operations.first_page_objects.fungible_page_objects.get_rgb20_asset_name( - RGB20_ASSET_NAME, - ) - assert asset_name == RGB20_ASSET_NAME - - -@pytest.mark.parametrize('test_environment', [False], indirect=True) -@allure.feature('Issue rgb20 asset with sufficient sats') -@allure.story('Issue rgb20 asset with sufficient sats which will create asset') -def test_issue_rgb20_with_sufficient_sats_and_utxo(wallets_and_operations: WalletTestSetup): - """ - Test rgb20 asset issuance with sufficient sats and utxo. - """ - - with allure.step('Verified that one utxo exists for issue rgb20 asset'): - wallets_and_operations.first_page_objects.sidebar_page_objects.click_view_unspents_button() - count = wallets_and_operations.first_page_objects.view_unspent_list_page_objects.get_unspent_widget() - rgb20_asset_id = wallets_and_operations.first_page_objects.view_unspent_list_page_objects.get_unspent_utxo_asset_id( - 'NA', - ) - wallets_and_operations.first_page_objects.sidebar_page_objects.click_fungibles_button() - assert count == 3 - assert rgb20_asset_id == 'NA' - - with allure.step('Issue rgb20 asset with sufficient sats and utxo'): - wallets_and_operations.first_page_features.issue_rgb20_features.issue_rgb20_with_sufficient_sats_and_utxo( - FIRST_APPLICATION, ASSET_TICKER, RGB20_ASSET_NAME, ASSET_AMOUNT, - ) - - with allure.step('Verify asset name'): - asset_name = wallets_and_operations.first_page_objects.fungible_page_objects.get_rgb20_asset_name( - RGB20_ASSET_NAME, - ) - assert asset_name == RGB20_ASSET_NAME diff --git a/e2e_tests/test/spec/test_issue_rgb25_asset.py b/e2e_tests/test/spec/test_issue_rgb25_asset.py deleted file mode 100644 index f3295e9c..00000000 --- a/e2e_tests/test/spec/test_issue_rgb25_asset.py +++ /dev/null @@ -1,106 +0,0 @@ -# pylint: disable=redefined-outer-name, unused-import -""" -Tests for issuing rgb25 assets with different scenarios. -""" -from __future__ import annotations - -import allure -import pytest - -from accessible_constant import FIRST_APPLICATION -from accessible_constant import FIRST_APPLICATION_URL -from e2e_tests.test.utilities.app_setup import test_environment -from e2e_tests.test.utilities.app_setup import wallets_and_operations -from e2e_tests.test.utilities.app_setup import WalletTestSetup -from src.model.enums.enums_model import WalletType - -RGB25_ASSET_NAME = 'Rgb25' -ASSET_DESCRIPTION = 'This is Rgb25 asset' -ASSET_AMOUNT = '2000' -ISSUE_RGB25_TOASTER_MESSAGE = 'You have insufficient funds' - - -@pytest.mark.parametrize('test_environment', [False], indirect=True) -@allure.feature('Issue rgb25 asset without sufficient sats') -@allure.story('Issue rgb25 asset without sufficient sats which will produce error toaster') -def test_issue_rgb25_without_sufficient_sats(wallets_and_operations: WalletTestSetup): - """ - Test issuing rgb25 asset without sufficient sats. - """ - - with allure.step('Create and fund first wallet for issue rgb25'): - wallets_and_operations.first_page_features.wallet_features.create_and_fund_wallet( - wallets_and_operations=wallets_and_operations, application=FIRST_APPLICATION, application_url=FIRST_APPLICATION_URL, fund=False, - ) - - with allure.step('Issue rgb25 asset without sat'): - description = wallets_and_operations.first_page_features.issue_rgb25_features.issue_rgb25_asset_without_sat( - FIRST_APPLICATION, RGB25_ASSET_NAME, ASSET_DESCRIPTION, ASSET_AMOUNT, - ) - - with allure.step('Verify toaster title and message'): - assert description == ISSUE_RGB25_TOASTER_MESSAGE - - -@pytest.mark.parametrize('test_environment', [False], indirect=True) -@allure.feature('Issue rgb25 asset with sufficient sats but no utxo') -@allure.story('Issue rgb25 asset with sufficient sats and no utxo which will first create utxo and then create asset') -def test_issue_rgb25_with_sufficient_sats_and_no_utxo(wallets_and_operations: WalletTestSetup): - """ - Test issuing rgb25 asset with sufficient sats but no utxo. - """ - - with allure.step('Fund wallet for issue rgb25 asset'): - wallets_and_operations.first_page_features.wallet_features.fund_wallet( - FIRST_APPLICATION, - ) - - with allure.step('Verifies there is no utxo for issue rgb25 asset'): - wallets_and_operations.first_page_objects.sidebar_page_objects.click_view_unspents_button() - count = wallets_and_operations.first_page_objects.view_unspent_list_page_objects.get_unspent_widget() - wallets_and_operations.first_page_objects.sidebar_page_objects.click_fungibles_button() - assert count == 1 - - with allure.step('Issue rgb25 with sufficient sats and no utxo'): - wallets_and_operations.first_page_features.issue_rgb25_features.issue_rgb25_with_sufficient_sats_and_no_utxo( - FIRST_APPLICATION, RGB25_ASSET_NAME, ASSET_DESCRIPTION, ASSET_AMOUNT, - ) - - with allure.step('Verify asset name'): - asset_name = wallets_and_operations.first_page_objects.collectible_page_objects.get_rgb25_asset_name( - RGB25_ASSET_NAME, - ) - assert asset_name == RGB25_ASSET_NAME - - -@pytest.mark.parametrize('test_environment', [False], indirect=True) -@allure.feature('Issue rgb25 asset with sufficient sats and utxo') -@allure.story('Issue rgb25 asset with sufficient sats and utxo which will create asset') -def test_issue_rgb25_with_sufficient_sats_and_utxo(wallets_and_operations: WalletTestSetup): - """ - Test issuing rgb25 asset with sufficient sats and utxo. - """ - - with allure.step('Verified that one utxo exists for issue rgb25 asset'): - wallets_and_operations.first_page_objects.sidebar_page_objects.click_view_unspents_button() - - count = wallets_and_operations.first_page_objects.view_unspent_list_page_objects.get_unspent_widget() - - asset_id = wallets_and_operations.first_page_objects.view_unspent_list_page_objects.get_unspent_utxo_asset_id( - 'NA', - ) - wallets_and_operations.first_page_objects.sidebar_page_objects.click_fungibles_button() - - assert count == 3 - assert asset_id == 'NA' - - with allure.step('Issue rgb25 with sufficient sats and utxo'): - wallets_and_operations.first_page_features.issue_rgb25_features.issue_rgb25_with_sufficient_sats_and_utxo( - FIRST_APPLICATION, RGB25_ASSET_NAME, ASSET_DESCRIPTION, ASSET_AMOUNT, - ) - - with allure.step('Verify asset name'): - asset_name = wallets_and_operations.first_page_objects.collectible_page_objects.get_rgb25_asset_name( - RGB25_ASSET_NAME, - ) - assert asset_name == RGB25_ASSET_NAME diff --git a/e2e_tests/test/spec/test_rgb20_channel_tx.py b/e2e_tests/test/spec/test_nia_channel_tx.py similarity index 87% rename from e2e_tests/test/spec/test_rgb20_channel_tx.py rename to e2e_tests/test/spec/test_nia_channel_tx.py index 67e0da34..61935af8 100644 --- a/e2e_tests/test/spec/test_rgb20_channel_tx.py +++ b/e2e_tests/test/spec/test_nia_channel_tx.py @@ -1,5 +1,5 @@ # pylint: disable=redefined-outer-name, unused-import, too-many-statements,unused-argument -"""Tests for send/receive RGB20 asset with channel""" +"""Tests for send/receive NIA asset with channel""" from __future__ import annotations import re @@ -17,7 +17,6 @@ from e2e_tests.test.utilities.app_setup import WalletTestSetup from e2e_tests.test.utilities.model import TransferType from e2e_tests.test.utilities.translation_utils import TranslationManager - ASSET_TICKER = 'TTK' ASSET_NAME = 'Tether' ASSET_AMOUNT = '2000' @@ -30,31 +29,27 @@ INVALID_AMOUNT = '120' -@allure.feature('Channel send and receive for RGB20 asset') -@allure.story('Send and receive with correct invoice for RGB20 asset') -def test_send_and_receive_with_correct_invoice_for_rgb20(wallets_and_operations: WalletTestSetup): - """Test sending and receiving with correct invoice for RGB20""" +@allure.feature('Channel send and receive for NIA asset') +@allure.story('Send and receive with correct invoice for NIA asset') +def test_send_and_receive_with_correct_invoice_for_nia(wallets_and_operations: WalletTestSetup): + """Test sending and receiving with correct invoice for NIA""" node_uri = None - - with allure.step('Create and fund first wallet for rgb20 channel'): + with allure.step('Create and fund first wallet for nia channel'): wallets_and_operations.first_page_features.wallet_features.create_and_fund_wallet( wallets_and_operations=wallets_and_operations, application=FIRST_APPLICATION, application_url=FIRST_APPLICATION_URL, ) - - with allure.step('Create and fund second wallet for rgb20 channel'): + with allure.step('Create and fund second wallet for nia channel'): wallets_and_operations.second_page_features.wallet_features.create_and_fund_wallet( wallets_and_operations=wallets_and_operations, application=SECOND_APPLICATION, application_url=SECOND_APPLICATION_URL, ) - - with allure.step('Issue RGB20 asset'): + with allure.step('Issue NIA asset'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_features.issue_rgb20_features.issue_rgb20_with_sufficient_sats_and_utxo( + wallets_and_operations.first_page_features.issue_nia_features.issue_nia_with_sufficient_sats_and_utxo( application=FIRST_APPLICATION, asset_ticker=ASSET_TICKER, asset_name=ASSET_NAME, asset_amount=ASSET_AMOUNT, ) - - with allure.step('Get node URI for rgb20 channel'): + with allure.step('Get node URI for nia channel'): if wallets_and_operations.wallet_mode == 'embedded': node_uri = wallets_and_operations.second_page_features.channel_features.get_node_uri_for_embedded( application=SECOND_APPLICATION, ip_address=IP_ADDRESS, @@ -63,21 +58,19 @@ def test_send_and_receive_with_correct_invoice_for_rgb20(wallets_and_operations: node_uri = wallets_and_operations.second_page_features.channel_features.get_node_uri_for_remote( application=SECOND_APPLICATION, ip_address=IP_ADDRESS, ln_port=LN_PORT, ) - - with allure.step('Create channel for rgb20'): + with allure.step('Create channel for nia'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) _channel_status = wallets_and_operations.first_page_features.channel_features.create_channel( application=FIRST_APPLICATION, node_uri=node_uri, asset_ticker=ASSET_TICKER, asset_amount=CREATE_ASSET_CHANNEL_WITH_AMOUNT, channel_capacity=CHANNEL_CAPACITY, ) - with allure.step('Create invoice'): wallets_and_operations.second_page_operations.do_focus_on_application( SECOND_APPLICATION, ) wallets_and_operations.second_page_objects.fungible_page_objects.click_refresh_button() - wallets_and_operations.second_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.second_page_objects.fungible_page_objects.click_nia_frame( asset_name=ASSET_NAME, ) wallets_and_operations.second_page_objects.asset_detail_page_objects.click_receive_button() @@ -85,25 +78,23 @@ def test_send_and_receive_with_correct_invoice_for_rgb20(wallets_and_operations: application=SECOND_APPLICATION, transfer_type=TransferType.LIGHTNING.value, value=INVOICE_AMOUNT, ) wallets_and_operations.second_page_objects.fungible_page_objects.click_refresh_button() - with allure.step('Send with invoice'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( asset_name=ASSET_NAME, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_send_button() wallets_and_operations.first_page_features.send_features.send( application=FIRST_APPLICATION, receiver_invoice=invoice, transfer_type=TransferType.LIGHTNING.value, ) - with allure.step('validate transaction amount'): wallets_and_operations.second_page_operations.do_focus_on_application( SECOND_APPLICATION, ) wallets_and_operations.second_page_objects.fungible_page_objects.click_refresh_button() - wallets_and_operations.second_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.second_page_objects.fungible_page_objects.click_nia_frame( asset_name=ASSET_NAME, ) wallets_and_operations.second_page_objects.asset_detail_page_objects.click_refresh_button() @@ -126,13 +117,12 @@ def test_create_wrong_amount_invoice(wallets_and_operations: WalletTestSetup, lo """ Test case to create wrong ln invoice with insufficient amount """ - with allure.step('Create invoice'): wallets_and_operations.second_page_operations.do_focus_on_application( SECOND_APPLICATION, ) wallets_and_operations.second_page_objects.fungible_page_objects.click_refresh_button() - wallets_and_operations.second_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.second_page_objects.fungible_page_objects.click_nia_frame( asset_name=ASSET_NAME, ) wallets_and_operations.second_page_objects.asset_detail_page_objects.click_receive_button() diff --git a/e2e_tests/test/spec/test_refresh_transfer.py b/e2e_tests/test/spec/test_refresh_transfer.py index c7bd3215..58b85ef6 100644 --- a/e2e_tests/test/spec/test_refresh_transfer.py +++ b/e2e_tests/test/spec/test_refresh_transfer.py @@ -34,8 +34,8 @@ def test_refresh_transfer(wallets_and_operations: WalletTestSetup): wallets_and_operations=wallets_and_operations, application=SECOND_APPLICATION, application_url=SECOND_APPLICATION_URL, ) - with allure.step('Issue RGB20 asset for refresh transfer'): - wallets_and_operations.first_page_features.issue_rgb20_features.issue_rgb20_with_sufficient_sats_and_utxo( + with allure.step('Issue NIA asset for refresh transfer'): + wallets_and_operations.first_page_features.issue_nia_features.issue_nia_with_sufficient_sats_and_utxo( application=FIRST_APPLICATION, asset_ticker=ASSET_TICKER, asset_name=ASSET_NAME, asset_amount=ASSET_AMOUNT, ) @@ -44,12 +44,12 @@ def test_refresh_transfer(wallets_and_operations: WalletTestSetup): SECOND_APPLICATION, ) - with allure.step('Send RGB20 asset to correct invoice'): + with allure.step('Send NIA asset to correct invoice'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( ASSET_NAME, ) @@ -81,7 +81,7 @@ def test_refresh_transfer(wallets_and_operations: WalletTestSetup): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( ASSET_NAME, ) actual_transfer_status_first_app = wallets_and_operations.first_page_objects.asset_detail_page_objects.get_transfer_status() @@ -89,7 +89,7 @@ def test_refresh_transfer(wallets_and_operations: WalletTestSetup): wallets_and_operations.second_page_operations.do_focus_on_application( SECOND_APPLICATION, ) - wallets_and_operations.second_page_objects.fungible_page_objects.click_rgb20_frame( + wallets_and_operations.second_page_objects.fungible_page_objects.click_nia_frame( ASSET_NAME, ) actual_transfer_status_second_app = wallets_and_operations.second_page_objects.asset_detail_page_objects.get_transfer_status() diff --git a/e2e_tests/test/spec/test_send_and_receive_rgb25_asset.py b/e2e_tests/test/spec/test_send_and_receive_cfa_asset.py similarity index 82% rename from e2e_tests/test/spec/test_send_and_receive_rgb25_asset.py rename to e2e_tests/test/spec/test_send_and_receive_cfa_asset.py index fe06cafd..9fed4edd 100644 --- a/e2e_tests/test/spec/test_send_and_receive_rgb25_asset.py +++ b/e2e_tests/test/spec/test_send_and_receive_cfa_asset.py @@ -1,5 +1,5 @@ # pylint: disable=redefined-outer-name, unused-import -"""Iris wallet send and receive operation automation test suite for rgb25 asset""" +"""Iris wallet send and receive operation automation test suite for cfa asset""" from __future__ import annotations import allure @@ -15,39 +15,39 @@ from e2e_tests.test.utilities.translation_utils import TranslationManager from src.model.enums.enums_model import TransactionStatusEnumModel -ASSET_NAME = 'RGB25' -ASSET_DESCRIPTION = 'This is rgb25 asset' +ASSET_NAME = 'CFA' +ASSET_DESCRIPTION = 'This is cfa asset' ASSET_AMOUNT = '2000' SEND_AMOUNT = '50' INVOICE = 'rgb:~/~/utxob:2msKeFq-uPjwpYxVY-jKS2ymYBq-SqmyP3ovg-AGvth8491-J7seMBm?expiry=1709616110&endpoints=rpc://10.0.2.2:3000/json-rpc' -@allure.feature('Automation of send operation for RGB25 asset in iris wallet') -@allure.story('Testing send RGB25 asset with expired invoice') -def test_send_rgb25_with_expired_invoice(wallets_and_operations: WalletTestSetup): - """Test send rgb25 asset with expired invoice""" +@allure.feature('Automation of send operation for CFA asset in iris wallet') +@allure.story('Testing send CFA asset with expired invoice') +def test_send_cfa_with_expired_invoice(wallets_and_operations: WalletTestSetup): + """Test send cfa asset with expired invoice""" - with allure.step('Create and fund first wallet for send and receive rgb25'): + with allure.step('Create and fund first wallet for send and receive cfa'): wallets_and_operations.first_page_features.wallet_features.create_and_fund_wallet( wallets_and_operations=wallets_and_operations, application=FIRST_APPLICATION, application_url=FIRST_APPLICATION_URL, ) - with allure.step('Create and fund second wallet for send and receive rgb25'): + with allure.step('Create and fund second wallet for send and receive cfa'): wallets_and_operations.second_page_features.wallet_features.create_and_fund_wallet( wallets_and_operations=wallets_and_operations, application=SECOND_APPLICATION, application_url=SECOND_APPLICATION_URL, ) - with allure.step('Issue RGB25 asset'): - wallets_and_operations.first_page_features.issue_rgb25_features.issue_rgb25_with_sufficient_sats_and_utxo( + with allure.step('Issue CFA asset'): + wallets_and_operations.first_page_features.issue_cfa_features.issue_cfa_with_sufficient_sats_and_utxo( application=FIRST_APPLICATION, asset_description=ASSET_DESCRIPTION, asset_name=ASSET_NAME, asset_amount=ASSET_AMOUNT, ) - with allure.step('Send RGB25 asset with expired invoice'): + with allure.step('Send CFA asset with expired invoice'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) wallets_and_operations.first_page_objects.sidebar_page_objects.click_collectibles_button() - wallets_and_operations.first_page_objects.collectible_page_objects.click_rgb25_frame( + wallets_and_operations.first_page_objects.collectible_page_objects.click_cfa_frame( ASSET_NAME, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_send_button() @@ -58,27 +58,27 @@ def test_send_rgb25_with_expired_invoice(wallets_and_operations: WalletTestSetup validation_label = wallets_and_operations.first_page_objects.send_asset_page_objects.get_asset_address_validation_label() wallets_and_operations.first_page_objects.send_asset_page_objects.click_send_asset_close_button() - with allure.step('Verify error message for rgb25 asset'): + with allure.step('Verify error message for cfa asset'): assert validation_label == TranslationManager.translate( 'invalid_invoice', ) -@allure.feature('Automation of receive, send, and transaction status for RGB25 asset in iris wallet') -@allure.story('End-to-End testing of receiving, sending, and verifying transaction status for RGB25 asset') -def test_send_and_receive_rgb25_asset_operation(wallets_and_operations: WalletTestSetup): - """Test send and receive operation for rgb25 asset""" +@allure.feature('Automation of receive, send, and transaction status for CFA asset in iris wallet') +@allure.story('End-to-End testing of receiving, sending, and verifying transaction status for CFA asset') +def test_send_and_receive_cfa_asset_operation(wallets_and_operations: WalletTestSetup): + """Test send and receive operation for cfa asset""" with allure.step('Generate invoice'): invoice = wallets_and_operations.second_page_features.receive_features.receive_asset_from_sidebar( SECOND_APPLICATION, ) - with allure.step('Send RGB25 asset'): + with allure.step('Send CFA asset'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_objects.collectible_page_objects.click_rgb25_frame( + wallets_and_operations.first_page_objects.collectible_page_objects.click_cfa_frame( ASSET_NAME, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_send_button() @@ -87,7 +87,7 @@ def test_send_and_receive_rgb25_asset_operation(wallets_and_operations: WalletTe ) with allure.step('Verify transfer status'): - wallets_and_operations.first_page_objects.collectible_page_objects.click_rgb25_frame( + wallets_and_operations.first_page_objects.collectible_page_objects.click_cfa_frame( ASSET_NAME, ) actual_transfer_status = wallets_and_operations.first_page_objects.asset_detail_page_objects.get_transfer_status() @@ -100,7 +100,7 @@ def test_send_and_receive_rgb25_asset_operation(wallets_and_operations: WalletTe ) wallets_and_operations.second_page_objects.collectible_page_objects.click_refresh_button() wallets_and_operations.second_page_objects.sidebar_page_objects.click_collectibles_button() - wallets_and_operations.second_page_objects.collectible_page_objects.click_rgb25_frame( + wallets_and_operations.second_page_objects.collectible_page_objects.click_cfa_frame( ASSET_NAME, ) received_amount = wallets_and_operations.second_page_objects.asset_detail_page_objects.get_on_chain_total_balance() diff --git a/e2e_tests/test/spec/test_send_and_receive_rgb20_asset.py b/e2e_tests/test/spec/test_send_and_receive_nia_asset.py similarity index 75% rename from e2e_tests/test/spec/test_send_and_receive_rgb20_asset.py rename to e2e_tests/test/spec/test_send_and_receive_nia_asset.py index a1b99293..176092ed 100644 --- a/e2e_tests/test/spec/test_send_and_receive_rgb20_asset.py +++ b/e2e_tests/test/spec/test_send_and_receive_nia_asset.py @@ -1,5 +1,5 @@ # pylint: disable=redefined-outer-name, unused-import, unused-argument -"""Iris wallet send and receive operation automation test suite for rgb20 asset""" +"""Iris wallet send and receive operation automation test suite for nia asset""" from __future__ import annotations import allure @@ -16,38 +16,38 @@ from src.model.enums.enums_model import TransactionStatusEnumModel ASSET_TICKER = 'TTK' -RGB20_ASSET_NAME = 'Tether' +NIA_ASSET_NAME = 'Tether' ASSET_AMOUNT = '2000' SEND_AMOUNT = '50' INVOICE = 'rgb:~/~/utxob:2msKeFq-uPjwpYxVY-jKS2ymYBq-SqmyP3ovg-AGvth8491-J7seMBm?expiry=1709616110&endpoints=rpc://10.0.2.2:3000/json-rpc' -@allure.feature('Automation of send operation for RGB20 asset in iris wallet') -@allure.story('Testing send RGB20 asset with expired invoice') -def test_send_rgb20_with_expired_invoice(wallets_and_operations: WalletTestSetup, load_qm_translation): - """Test send rgb20 asset with expired invoice""" +@allure.feature('Automation of send operation for NIA asset in iris wallet') +@allure.story('Testing send NIA asset with expired invoice') +def test_send_nia_with_expired_invoice(wallets_and_operations: WalletTestSetup, load_qm_translation): + """Test send nia asset with expired invoice""" validation_label = None - with allure.step('Create and fund first wallet for send and receive rgb20'): + with allure.step('Create and fund first wallet for send and receive nia'): wallets_and_operations.first_page_features.wallet_features.create_and_fund_wallet( wallets_and_operations=wallets_and_operations, application=FIRST_APPLICATION, application_url=FIRST_APPLICATION_URL, ) - with allure.step('Create and fund second wallet for send and receive rgb20'): + with allure.step('Create and fund second wallet for send and receive nia'): wallets_and_operations.second_page_features.wallet_features.create_and_fund_wallet( wallets_and_operations=wallets_and_operations, application=SECOND_APPLICATION, application_url=SECOND_APPLICATION_URL, ) - with allure.step('Issue RGB20 asset'): - wallets_and_operations.first_page_features.issue_rgb20_features.issue_rgb20_with_sufficient_sats_and_utxo( - application=FIRST_APPLICATION, asset_ticker=ASSET_TICKER, asset_name=RGB20_ASSET_NAME, asset_amount=ASSET_AMOUNT, + with allure.step('Issue NIA asset'): + wallets_and_operations.first_page_features.issue_nia_features.issue_nia_with_sufficient_sats_and_utxo( + application=FIRST_APPLICATION, asset_ticker=ASSET_TICKER, asset_name=NIA_ASSET_NAME, asset_amount=ASSET_AMOUNT, ) - with allure.step('Send RGB20 asset with expired invoice'): + with allure.step('Send NIA asset with expired invoice'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_objects.fungible_page_objects.click_rgb20_frame( - RGB20_ASSET_NAME, + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( + NIA_ASSET_NAME, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_send_button() wallets_and_operations.first_page_objects.send_asset_page_objects.enter_asset_invoice( @@ -56,20 +56,20 @@ def test_send_rgb20_with_expired_invoice(wallets_and_operations: WalletTestSetup validation_label = wallets_and_operations.first_page_objects.send_asset_page_objects.get_asset_address_validation_label() wallets_and_operations.first_page_objects.send_asset_page_objects.click_send_asset_close_button() - with allure.step('Verify error message for rgb20 asset'): + with allure.step('Verify error message for nia asset'): assert validation_label == TranslationManager.translate( 'invalid_invoice', ) -@allure.feature('Automation of receive, send, and transaction status for RGB20 asset in iris wallet') -@allure.story('End-to-End testing of receiving, sending, and verifying transaction status for RGB20 asset') -def test_send_and_receive_rgb20_asset_operation(wallets_and_operations: WalletTestSetup): - """Test send and receive operation for rgb20 asset""" +@allure.feature('Automation of receive, send, and transaction status for NIA asset in iris wallet') +@allure.story('End-to-End testing of receiving, sending, and verifying transaction status for NIA asset') +def test_send_and_receive_nia_asset_operation(wallets_and_operations: WalletTestSetup): + """Test send and receive operation for nia asset""" - with allure.step('Issue RGB20 asset'): - wallets_and_operations.first_page_features.issue_rgb20_features.issue_rgb20_with_sufficient_sats_and_utxo( - application=FIRST_APPLICATION, asset_ticker=ASSET_TICKER, asset_name=RGB20_ASSET_NAME, asset_amount=ASSET_AMOUNT, + with allure.step('Issue NIA asset'): + wallets_and_operations.first_page_features.issue_nia_features.issue_nia_with_sufficient_sats_and_utxo( + application=FIRST_APPLICATION, asset_ticker=ASSET_TICKER, asset_name=NIA_ASSET_NAME, asset_amount=ASSET_AMOUNT, ) with allure.step('Generate invoice'): @@ -77,12 +77,12 @@ def test_send_and_receive_rgb20_asset_operation(wallets_and_operations: WalletTe SECOND_APPLICATION, ) - with allure.step('Send RGB20 asset'): + with allure.step('Send NIA asset'): wallets_and_operations.first_page_operations.do_focus_on_application( FIRST_APPLICATION, ) - wallets_and_operations.first_page_objects.fungible_page_objects.click_rgb20_frame( - RGB20_ASSET_NAME, + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( + NIA_ASSET_NAME, ) wallets_and_operations.first_page_objects.asset_detail_page_objects.click_send_button() wallets_and_operations.first_page_features.send_features.send( @@ -90,8 +90,8 @@ def test_send_and_receive_rgb20_asset_operation(wallets_and_operations: WalletTe ) with allure.step('Verify transaction status'): - wallets_and_operations.first_page_objects.fungible_page_objects.click_rgb20_frame( - RGB20_ASSET_NAME, + wallets_and_operations.first_page_objects.fungible_page_objects.click_nia_frame( + NIA_ASSET_NAME, ) actual_transfer_status = wallets_and_operations.first_page_objects.asset_detail_page_objects.get_transfer_status() wallets_and_operations.first_page_objects.asset_detail_page_objects.click_close_button() @@ -101,8 +101,8 @@ def test_send_and_receive_rgb20_asset_operation(wallets_and_operations: WalletTe SECOND_APPLICATION, ) wallets_and_operations.second_page_objects.fungible_page_objects.click_refresh_button() - wallets_and_operations.second_page_objects.fungible_page_objects.click_rgb20_frame( - RGB20_ASSET_NAME, + wallets_and_operations.second_page_objects.fungible_page_objects.click_nia_frame( + NIA_ASSET_NAME, ) received_amount = wallets_and_operations.second_page_objects.asset_detail_page_objects.get_on_chain_total_balance() wallets_and_operations.second_page_objects.asset_detail_page_objects.click_close_button() diff --git a/e2e_tests/test/spec/test_view_unspent.py b/e2e_tests/test/spec/test_view_unspent.py index bc1ffb20..52fb914c 100644 --- a/e2e_tests/test/spec/test_view_unspent.py +++ b/e2e_tests/test/spec/test_view_unspent.py @@ -12,7 +12,6 @@ from e2e_tests.test.utilities.app_setup import test_environment from e2e_tests.test.utilities.app_setup import wallets_and_operations from e2e_tests.test.utilities.app_setup import WalletTestSetup -from src.model.enums.enums_model import WalletType @pytest.mark.parametrize('test_environment', [False], indirect=True) diff --git a/e2e_tests/test/utilities/app_setup.py b/e2e_tests/test/utilities/app_setup.py index 8a599a28..4f752677 100644 --- a/e2e_tests/test/utilities/app_setup.py +++ b/e2e_tests/test/utilities/app_setup.py @@ -134,6 +134,7 @@ def start_rgb_node(self, data_dir, daemon_port, peer_port): str(peer_port), '--network', 'regtest', + '--disable-authentication', ] process = subprocess.Popen(command, cwd=ln_binary_dir) return process diff --git a/e2e_tests/test/utilities/asset_copy.py b/e2e_tests/test/utilities/asset_copy.py index 3ce53119..f1e79ebf 100644 --- a/e2e_tests/test/utilities/asset_copy.py +++ b/e2e_tests/test/utilities/asset_copy.py @@ -9,7 +9,7 @@ from pathlib import Path -def copy_rgb25_image_to_home_directory(current_working_directory): +def copy_cfa_image_to_home_directory(current_working_directory): """ Copies the sample.png image from the e2e_tests/assets directory to the home directory. diff --git a/poetry.lock b/poetry.lock index 28127458..67337ed2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,33 +1,29 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "allure-pytest" -version = "2.14.0" +version = "2.15.3" description = "Allure pytest integration" optional = false python-versions = "*" -groups = ["dev"] -markers = "sys_platform == \"linux\"" files = [ - {file = "allure_pytest-2.14.0-py3-none-any.whl", hash = "sha256:6ddb68ef42bd5a2dfbcc136a184bf3e78e631ede7b8c54750026ffd407bda9af"}, - {file = "allure_pytest-2.14.0.tar.gz", hash = "sha256:2b485dc307755f8f3207783a69558ca1cc72f1e2c97bedc65c93fdb77adf328f"}, + {file = "allure_pytest-2.15.3-py3-none-any.whl", hash = "sha256:66711efdc44c7c0d629f4eeab73ca20932f8786c251d61e242449f7b4ec18ca2"}, + {file = "allure_pytest-2.15.3.tar.gz", hash = "sha256:b6576f8258de44f48ec59cfaee73cdbd1142c2ad6cfdc82d894fd1db8abf22e9"}, ] [package.dependencies] -allure-python-commons = "2.14.0" +allure-python-commons = "2.15.3" pytest = ">=4.5.0" [[package]] name = "allure-python-commons" -version = "2.14.0" +version = "2.15.3" description = "Contains the API for end users as well as helper functions and classes to build Allure adapters for Python test frameworks" optional = false python-versions = ">=3.6" -groups = ["dev"] -markers = "sys_platform == \"linux\"" files = [ - {file = "allure_python_commons-2.14.0-py3-none-any.whl", hash = "sha256:9200f40abee697133e9ed9f68887cde996a24b9eb33fcf528da8fe50fae88e43"}, - {file = "allure_python_commons-2.14.0.tar.gz", hash = "sha256:9b217e2f6c74cdbd0e253f89059d4165346e95fcb28228fae333ff4dccea0bd5"}, + {file = "allure_python_commons-2.15.3-py3-none-any.whl", hash = "sha256:50e9b346d8a060c84af8d19f221bd9da6e1aa0002a4e7f770e151167365219d0"}, + {file = "allure_python_commons-2.15.3.tar.gz", hash = "sha256:b42a96d6076fb323c9e43645dfb84c0574f6bad0a0e005d92564015cd172d564"}, ] [package.dependencies] @@ -36,14 +32,13 @@ pluggy = ">=0.4.0" [[package]] name = "altgraph" -version = "0.17.4" +version = "0.17.5" description = "Python graph (network) package" optional = false python-versions = "*" -groups = ["dev"] files = [ - {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"}, - {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, + {file = "altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597"}, + {file = "altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7"}, ] [[package]] @@ -52,7 +47,6 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -64,7 +58,6 @@ version = "3.1.0" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.8.0" -groups = ["dev"] files = [ {file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"}, {file = "astroid-3.1.0.tar.gz", hash = "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"}, @@ -79,7 +72,6 @@ version = "3.4.3" description = "reference implementation of PEP 3156" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "asyncio-3.4.3-cp33-none-win32.whl", hash = "sha256:b62c9157d36187eca799c378e572c969f0da87cd5fc42ca372d92cdb06e7e1de"}, {file = "asyncio-3.4.3-cp33-none-win_amd64.whl", hash = "sha256:c46a87b48213d7464f22d9a497b9eef8c1928b68320a2fa94240f969f6fec08c"}, @@ -89,36 +81,25 @@ files = [ [[package]] name = "attrs" -version = "25.3.0" +version = "25.4.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, - {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, + {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, + {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, ] -[package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] - [[package]] name = "backports-tarfile" version = "1.2.0" description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"}, ] -markers = {main = "python_version < \"3.12\"", dev = "python_version < \"3.12\" and sys_platform == \"linux\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] @@ -130,7 +111,6 @@ version = "0.3.post4" description = "Utilities for generating and using Bitcoin Hierarchical Deterministic wallets (BIP0032)." optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "bip32utils-0.3.post4-py3-none-any.whl", hash = "sha256:86125e16732101f17dbf4307ca0311a06ded0aca94220ac961e5bce0a444e972"}, {file = "bip32utils-0.3.post4.tar.gz", hash = "sha256:5970f40fbb727a89d3cc06b6387b348252f7c8af6b3470df704276de728c48c2"}, @@ -145,7 +125,6 @@ version = "24.4.1" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "black-24.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f7749fd0d97ff9415975a1432fac7df89bf13c3833cea079e55fa004d5f28c0"}, {file = "black-24.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859f3cc5d2051adadf8fd504a01e02b0fd866d7549fff54bc9202d524d2e8bd7"}, @@ -182,7 +161,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -192,256 +171,275 @@ version = "1.0.1" description = "Version-bump your software with a single command!" optional = false python-versions = ">=3.5" -groups = ["dev"] files = [ {file = "bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410"}, {file = "bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"}, ] -[[package]] -name = "cachetools" -version = "5.5.2" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, - {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, -] - [[package]] name = "cattrs" -version = "24.1.3" +version = "25.3.0" description = "Composable complex class support for attrs and dataclasses." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "cattrs-24.1.3-py3-none-any.whl", hash = "sha256:adf957dddd26840f27ffbd060a6c4dd3b2192c5b7c2c0525ef1bd8131d8a83f5"}, - {file = "cattrs-24.1.3.tar.gz", hash = "sha256:981a6ef05875b5bb0c7fb68885546186d306f10f0f6718fe9b96c226e68821ff"}, + {file = "cattrs-25.3.0-py3-none-any.whl", hash = "sha256:9896e84e0a5bf723bc7b4b68f4481785367ce07a8a02e7e9ee6eb2819bc306ff"}, + {file = "cattrs-25.3.0.tar.gz", hash = "sha256:1ac88d9e5eda10436c4517e390a4142d88638fe682c436c93db7ce4a277b884a"}, ] [package.dependencies] -attrs = ">=23.1.0" +attrs = ">=25.4.0" exceptiongroup = {version = ">=1.1.1", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.1.0,<4.6.3 || >4.6.3", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.14.0" [package.extras] bson = ["pymongo (>=4.4.0)"] cbor2 = ["cbor2 (>=5.4.6)"] msgpack = ["msgpack (>=1.0.5)"] -msgspec = ["msgspec (>=0.18.5) ; implementation_name == \"cpython\""] -orjson = ["orjson (>=3.9.2) ; implementation_name == \"cpython\""] +msgspec = ["msgspec (>=0.19.0)"] +orjson = ["orjson (>=3.11.3)"] pyyaml = ["pyyaml (>=6.0)"] tomlkit = ["tomlkit (>=0.11.8)"] -ujson = ["ujson (>=5.7.0)"] +ujson = ["ujson (>=5.10.0)"] [[package]] name = "certifi" -version = "2025.1.31" +version = "2026.1.4" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] +python-versions = ">=3.7" files = [ - {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, - {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, + {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, + {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, ] [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -markers = "platform_python_implementation != \"PyPy\"" -files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +python-versions = ">=3.9" +files = [ + {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, + {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, + {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, + {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, + {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, + {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, + {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, + {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, + {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, + {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, + {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, + {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, + {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, + {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, + {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, + {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, + {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, + {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, + {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, + {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, + {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] [package.dependencies] -pycparser = "*" +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} [[package]] name = "cfgv" -version = "3.4.0" +version = "3.5.0" description = "Validate configuration and produce human readable error messages." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.10" files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, + {file = "cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0"}, + {file = "cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132"}, ] [[package]] name = "charset-normalizer" -version = "3.4.1" +version = "3.4.4" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, - {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, - {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, +files = [ + {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, + {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, + {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, ] [[package]] @@ -450,7 +448,6 @@ version = "2.1.0" description = "Cleo allows you to create beautiful and testable command-line interfaces." optional = false python-versions = ">=3.7,<4.0" -groups = ["main"] files = [ {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, @@ -462,14 +459,13 @@ rapidfuzz = ">=3.0.0,<4.0.0" [[package]] name = "click" -version = "8.1.8" +version = "8.3.1" description = "Composable command line interface toolkit" optional = false -python-versions = ">=3.7" -groups = ["dev"] +python-versions = ">=3.10" files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, + {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, + {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, ] [package.dependencies] @@ -481,91 +477,117 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\" or platform_system == \"Windows\"", test = "sys_platform == \"win32\""} [[package]] name = "coverage" -version = "7.8.0" +version = "7.13.1" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.9" -groups = ["test"] -files = [ - {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"}, - {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"}, - {file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"}, - {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"}, - {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"}, - {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"}, - {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"}, - {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"}, - {file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"}, - {file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"}, - {file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"}, - {file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"}, - {file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"}, - {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"}, - {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"}, - {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"}, - {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"}, - {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"}, - {file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"}, - {file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"}, - {file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"}, - {file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"}, - {file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"}, - {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"}, - {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"}, - {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"}, - {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"}, - {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"}, - {file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"}, - {file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"}, - {file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"}, - {file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"}, - {file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"}, - {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"}, - {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"}, - {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"}, - {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"}, - {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"}, - {file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"}, - {file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"}, - {file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"}, - {file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"}, - {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"}, - {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"}, - {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"}, - {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"}, - {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"}, - {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"}, - {file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"}, - {file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"}, - {file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"}, - {file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"}, - {file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"}, - {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"}, - {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"}, - {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"}, - {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"}, - {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"}, - {file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"}, - {file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"}, - {file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"}, - {file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"}, - {file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"}, +python-versions = ">=3.10" +files = [ + {file = "coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147"}, + {file = "coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d"}, + {file = "coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae"}, + {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29"}, + {file = "coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f"}, + {file = "coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1"}, + {file = "coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88"}, + {file = "coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf"}, + {file = "coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb"}, + {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba"}, + {file = "coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19"}, + {file = "coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a"}, + {file = "coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c"}, + {file = "coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3"}, + {file = "coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968"}, + {file = "coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf"}, + {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c"}, + {file = "coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7"}, + {file = "coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6"}, + {file = "coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c"}, + {file = "coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78"}, + {file = "coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4"}, + {file = "coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398"}, + {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784"}, + {file = "coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461"}, + {file = "coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500"}, + {file = "coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9"}, + {file = "coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc"}, + {file = "coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1"}, + {file = "coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e"}, + {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53"}, + {file = "coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842"}, + {file = "coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2"}, + {file = "coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09"}, + {file = "coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894"}, + {file = "coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4"}, + {file = "coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864"}, + {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9"}, + {file = "coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5"}, + {file = "coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a"}, + {file = "coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0"}, + {file = "coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a"}, + {file = "coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d"}, + {file = "coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7"}, + {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416"}, + {file = "coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f"}, + {file = "coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79"}, + {file = "coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4"}, + {file = "coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573"}, + {file = "coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] +toml = ["tomli"] [[package]] name = "crashtest" @@ -573,7 +595,6 @@ version = "0.4.1" description = "Manage Python errors with ease" optional = false python-versions = ">=3.7,<4.0" -groups = ["main"] files = [ {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, @@ -585,7 +606,6 @@ version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, @@ -631,14 +651,13 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "dill" -version = "0.3.9" +version = "0.4.0" description = "serialize all of Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, - {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, + {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"}, + {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"}, ] [package.extras] @@ -647,14 +666,13 @@ profile = ["gprof2dot (>=2022.7.29)"] [[package]] name = "distlib" -version = "0.3.9" +version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["dev"] files = [ - {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, - {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] [[package]] @@ -663,8 +681,6 @@ version = "1.0.7" description = "GUI test tool and automation framework that uses Accessibility (a11y) technologies to communicate with desktop applications." optional = false python-versions = "*" -groups = ["dev"] -markers = "sys_platform == \"linux\"" files = [ {file = "dogtail-1.0.7-py3-none-any.whl", hash = "sha256:efbb225cbdbe0e6fb3b9aa5cf479d1907355126027b79973ea62263db4c5ad3c"}, {file = "dogtail-1.0.7.tar.gz", hash = "sha256:55447073c962ef5d1845d11972e21ac9579240490de82b4e3770b9a8c3a4fb3b"}, @@ -676,7 +692,6 @@ version = "0.19.1" description = "ECDSA cryptographic signature library (pure python)" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.6" -groups = ["dev"] files = [ {file = "ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3"}, {file = "ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61"}, @@ -691,30 +706,30 @@ gmpy2 = ["gmpy2"] [[package]] name = "exceptiongroup" -version = "1.2.2" +version = "1.3.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["dev", "test"] -markers = "python_version == \"3.10\"" files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + [package.extras] test = ["pytest (>=6)"] [[package]] name = "execnet" -version = "2.1.1" +version = "2.1.2" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, - {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, + {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, + {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, ] [package.extras] @@ -722,31 +737,24 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "filelock" -version = "3.18.0" +version = "3.20.3" description = "A platform independent file lock." optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = ">=3.10" files = [ - {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, - {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, + {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, + {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, ] -[package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] - [[package]] name = "google-api-core" -version = "2.24.2" +version = "2.29.0" description = "Google API client core library" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ - {file = "google_api_core-2.24.2-py3-none-any.whl", hash = "sha256:810a63ac95f3c441b7c0e43d344e372887f62ce9071ba972eacf32672e072de9"}, - {file = "google_api_core-2.24.2.tar.gz", hash = "sha256:81718493daf06d96d6bc76a91c23874dbf2fac0adbbf542831b805ee6e974696"}, + {file = "google_api_core-2.29.0-py3-none-any.whl", hash = "sha256:d30bc60980daa36e314b5d5a3e5958b0200cb44ca8fa1be2b614e932b75a3ea9"}, + {file = "google_api_core-2.29.0.tar.gz", hash = "sha256:84181be0f8e6b04006df75ddfe728f24489f0af57c96a529ff7cf45bc28797f7"}, ] [package.dependencies] @@ -757,21 +765,20 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4 requests = ">=2.18.0,<3.0.0" [package.extras] -async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.0)"] +grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0)", "grpcio (>=1.75.1,<2.0.0)", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0)", "grpcio-status (>=1.75.1,<2.0.0)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] [[package]] name = "google-api-python-client" -version = "2.166.0" +version = "2.187.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ - {file = "google_api_python_client-2.166.0-py2.py3-none-any.whl", hash = "sha256:dd8cc74d9fc18538ab05cbd2e93cb4f82382f910c5f6945db06c91f1deae6e45"}, - {file = "google_api_python_client-2.166.0.tar.gz", hash = "sha256:b8cf843bd9d736c134aef76cf1dc7a47c9283a2ef24267b97207b9dd43b30ef7"}, + {file = "google_api_python_client-2.187.0-py3-none-any.whl", hash = "sha256:d8d0f6d85d7d1d10bdab32e642312ed572bdc98919f72f831b44b9a9cebba32f"}, + {file = "google_api_python_client-2.187.0.tar.gz", hash = "sha256:e98e8e8f49e1b5048c2f8276473d6485febc76c9c47892a8b4d1afa2c9ec8278"}, ] [package.dependencies] @@ -783,55 +790,54 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "2.38.0" +version = "2.47.0" description = "Google Authentication Library" optional = false -python-versions = ">=3.7" -groups = ["dev"] +python-versions = ">=3.8" files = [ - {file = "google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a"}, - {file = "google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4"}, + {file = "google_auth-2.47.0-py3-none-any.whl", hash = "sha256:c516d68336bfde7cf0da26aab674a36fedcf04b37ac4edd59c597178760c3498"}, + {file = "google_auth-2.47.0.tar.gz", hash = "sha256:833229070a9dfee1a353ae9877dcd2dec069a8281a4e72e72f77d4a70ff945da"}, ] [package.dependencies] -cachetools = ">=2.0.0,<6.0" pyasn1-modules = ">=0.2.1" rsa = ">=3.1.4,<5" [package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] +cryptography = ["cryptography (>=38.0.3)"] enterprise-cert = ["cryptography", "pyopenssl"] pyjwt = ["cryptography (>=38.0.3)", "pyjwt (>=2.0)"] pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] +requests = ["requests (>=2.20.0,<3.0.0)"] +testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (>=38.0.3)", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] +urllib3 = ["packaging", "urllib3"] [[package]] name = "google-auth-httplib2" -version = "0.2.0" +version = "0.2.1" description = "Google Authentication Library: httplib2 transport" optional = false -python-versions = "*" -groups = ["dev"] +python-versions = ">=3.7" files = [ - {file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"}, - {file = "google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d"}, + {file = "google_auth_httplib2-0.2.1-py3-none-any.whl", hash = "sha256:1be94c611db91c01f9703e7f62b0a59bbd5587a95571c7b6fade510d648bc08b"}, + {file = "google_auth_httplib2-0.2.1.tar.gz", hash = "sha256:5ef03be3927423c87fb69607b42df23a444e434ddb2555b73b3679793187b7de"}, ] [package.dependencies] -google-auth = "*" -httplib2 = ">=0.19.0" +google-auth = ">=1.32.0,<3.0.0" +httplib2 = ">=0.19.0,<1.0.0" [[package]] name = "google-auth-oauthlib" -version = "1.2.1" +version = "1.2.2" description = "Google Authentication Library" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ - {file = "google_auth_oauthlib-1.2.1-py2.py3-none-any.whl", hash = "sha256:2d58a27262d55aa1b87678c3ba7142a080098cbc2024f903c62355deb235d91f"}, - {file = "google_auth_oauthlib-1.2.1.tar.gz", hash = "sha256:afd0cad092a2eaa53cd8e8298557d6de1034c6cb4a740500b5357b648af97263"}, + {file = "google_auth_oauthlib-1.2.2-py3-none-any.whl", hash = "sha256:fd619506f4b3908b5df17b65f39ca8d66ea56986e5472eb5978fd8f3786f00a2"}, + {file = "google_auth_oauthlib-1.2.2.tar.gz", hash = "sha256:11046fb8d3348b296302dd939ace8af0a724042e8029c1b872d87fabc9f41684"}, ] [package.dependencies] @@ -843,14 +849,13 @@ tool = ["click (>=6.0.0)"] [[package]] name = "googleapis-common-protos" -version = "1.69.2" +version = "1.72.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ - {file = "googleapis_common_protos-1.69.2-py3-none-any.whl", hash = "sha256:0b30452ff9c7a27d80bfc5718954063e8ab53dd3697093d3bc99581f5fd24212"}, - {file = "googleapis_common_protos-1.69.2.tar.gz", hash = "sha256:3e1b904a27a33c821b4b749fd31d334c0c9c30e6113023d495e48979a3dc9c5f"}, + {file = "googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038"}, + {file = "googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5"}, ] [package.dependencies] @@ -861,29 +866,27 @@ grpc = ["grpcio (>=1.44.0,<2.0.0)"] [[package]] name = "httplib2" -version = "0.22.0" +version = "0.31.0" description = "A comprehensive HTTP client library." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["dev"] +python-versions = ">=3.6" files = [ - {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"}, - {file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"}, + {file = "httplib2-0.31.0-py3-none-any.whl", hash = "sha256:b9cd78abea9b4e43a7714c6e0f8b6b8561a6fc1e95d5dbd367f5bf0ef35f5d24"}, + {file = "httplib2-0.31.0.tar.gz", hash = "sha256:ac7ab497c50975147d4f7b1ade44becc7df2f8954d42b38b3d69c515f531135c"}, ] [package.dependencies] -pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""} +pyparsing = ">=3.0.4,<4" [[package]] name = "identify" -version = "2.6.9" +version = "2.6.16" description = "File identification library for Python" optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = ">=3.10" files = [ - {file = "identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150"}, - {file = "identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf"}, + {file = "identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0"}, + {file = "identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980"}, ] [package.extras] @@ -891,14 +894,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.10" +version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] +python-versions = ">=3.8" files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, ] [package.extras] @@ -906,28 +908,26 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "importlib-metadata" -version = "8.6.1" +version = "8.7.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, - {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, + {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, + {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, ] -markers = {main = "python_version < \"3.12\""} [package.dependencies] zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +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)"] +enabler = ["pytest-enabler (>=3.4)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] +test = ["flufl.flake8", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["mypy (<1.19)", "pytest-mypy (>=1.0.1)"] [[package]] name = "importlib-resources" @@ -935,14 +935,13 @@ version = "6.5.2" description = "Read resources from Python packages" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}, {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +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)"] @@ -951,14 +950,13 @@ type = ["pytest-mypy"] [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.8" -groups = ["dev", "test"] +python-versions = ">=3.10" files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, ] [[package]] @@ -967,7 +965,6 @@ version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" -groups = ["dev"] files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -982,12 +979,10 @@ version = "3.4.0" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, ] -markers = {dev = "sys_platform == \"linux\""} [package.dependencies] more-itertools = "*" @@ -998,46 +993,47 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-ena [[package]] name = "jaraco-context" -version = "6.0.1" +version = "6.1.0" description = "Useful decorators and context managers" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, - {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, + {file = "jaraco_context-6.1.0-py3-none-any.whl", hash = "sha256:a43b5ed85815223d0d3cfdb6d7ca0d2bc8946f28f30b6f3216bda070f68badda"}, + {file = "jaraco_context-6.1.0.tar.gz", hash = "sha256:129a341b0a85a7db7879e22acd66902fda67882db771754574338898b2d5d86f"}, ] -markers = {dev = "sys_platform == \"linux\""} [package.dependencies] "backports.tarfile" = {version = "*", markers = "python_version < \"3.12\""} [package.extras] +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"] -test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +enabler = ["pytest-enabler (>=3.4)"] +test = ["jaraco.test (>=5.6.0)", "portend", "pytest (>=6,!=8.1.*)"] +type = ["mypy (<1.19)", "pytest-mypy (>=1.0.1)"] [[package]] name = "jaraco-functools" -version = "4.1.0" +version = "4.4.0" description = "Functools like those found in stdlib" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.9" files = [ - {file = "jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649"}, - {file = "jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d"}, + {file = "jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176"}, + {file = "jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb"}, ] [package.dependencies] -more-itertools = "*" +more_itertools = "*" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +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)"] +enabler = ["pytest-enabler (>=3.4)"] test = ["jaraco.classes", "pytest (>=6,!=8.1.*)"] -type = ["pytest-mypy"] +type = ["mypy (<1.19)", "pytest-mypy (>=1.0.1)"] [[package]] name = "jeepney" @@ -1045,15 +1041,13 @@ version = "0.9.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" -groups = ["main"] -markers = "sys_platform == \"linux\"" files = [ {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, {file = "jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732"}, ] [package.extras] -test = ["async-timeout ; python_version < \"3.11\"", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] trio = ["trio"] [[package]] @@ -1062,7 +1056,6 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["test"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1080,7 +1073,6 @@ version = "25.2.1" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "keyring-25.2.1-py3-none-any.whl", hash = "sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50"}, {file = "keyring-25.2.1.tar.gz", hash = "sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b"}, @@ -1106,8 +1098,6 @@ version = "5.0.2" description = "Alternate keyring implementations" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "sys_platform == \"linux\"" files = [ {file = "keyrings.alt-5.0.2-py3-none-any.whl", hash = "sha256:6be74693192f3f37bbb752bfac9b86e6177076b17d2ac12a390f1d6abff8ac7c"}, {file = "keyrings_alt-5.0.2.tar.gz", hash = "sha256:8f097ebe9dc8b185106502b8cdb066c926d2180e13b4689fd4771a3eab7d69fb"}, @@ -1119,19 +1109,17 @@ files = [ [package.extras] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["gdata ; python_version == \"2.7\"", "keyring (>=20)", "pycryptodome", "pycryptodomex", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "python-keyczar ; python_version == \"2.7\""] +test = ["gdata", "keyring (>=20)", "pycryptodome", "pycryptodomex", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "python-keyczar"] [[package]] name = "macholib" -version = "1.16.3" +version = "1.16.4" description = "Mach-O header analysis and editing" optional = false python-versions = "*" -groups = ["dev"] -markers = "sys_platform == \"darwin\"" files = [ - {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"}, - {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"}, + {file = "macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea"}, + {file = "macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362"}, ] [package.dependencies] @@ -1139,73 +1127,100 @@ altgraph = ">=0.17" [[package]] name = "markupsafe" -version = "3.0.2" +version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["test"] -files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +files = [ + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, ] [[package]] @@ -1214,7 +1229,6 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -1226,7 +1240,6 @@ version = "0.21" description = "Implementation of Bitcoin BIP-0039" optional = false python-versions = ">=3.8.1" -groups = ["dev"] files = [ {file = "mnemonic-0.21-py3-none-any.whl", hash = "sha256:72dc9de16ec5ef47287237b9b6943da11647a03fe7cf1f139fc3d7c4a7439288"}, {file = "mnemonic-0.21.tar.gz", hash = "sha256:1fe496356820984f45559b1540c80ff10de448368929b9c60a2b55744cc88acf"}, @@ -1234,51 +1247,46 @@ files = [ [[package]] name = "more-itertools" -version = "10.6.0" +version = "10.8.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b"}, - {file = "more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89"}, + {file = "more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"}, + {file = "more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"}, ] -markers = {dev = "sys_platform == \"linux\""} [[package]] name = "mypy-extensions" -version = "1.0.0" +version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = ">=3.5" -groups = ["dev"] +python-versions = ">=3.8" files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] name = "nodeenv" -version = "1.9.1" +version = "1.10.0" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, + {file = "nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827"}, + {file = "nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb"}, ] [[package]] name = "oauthlib" -version = "3.2.2" +version = "3.3.1" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" optional = false -python-versions = ">=3.6" -groups = ["dev"] +python-versions = ">=3.8" files = [ - {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, - {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, + {file = "oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1"}, + {file = "oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9"}, ] [package.extras] @@ -1288,39 +1296,41 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "packaging" -version = "24.2" +version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["dev", "test"] files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] name = "pathspec" -version = "0.12.1" +version = "1.0.3" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, + {file = "pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c"}, + {file = "pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d"}, ] +[package.extras] +hyperscan = ["hyperscan (>=0.7)"] +optional = ["typing-extensions (>=4)"] +re2 = ["google-re2 (>=1.1)"] +tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] + [[package]] name = "pefile" -version = "2023.2.7" +version = "2024.8.26" description = "Python PE parsing module" optional = false python-versions = ">=3.6.0" -groups = ["dev"] -markers = "sys_platform == \"win32\"" files = [ - {file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"}, - {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"}, + {file = "pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f"}, + {file = "pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632"}, ] [[package]] @@ -1329,7 +1339,6 @@ version = "10.4.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, @@ -1418,41 +1427,39 @@ docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions ; python_version < \"3.10\""] +typing = ["typing-extensions"] xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.3.7" +version = "4.5.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = ">=3.10" files = [ - {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, - {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, + {file = "platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31"}, + {file = "platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.14.1)"] +docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] +type = ["mypy (>=1.18.2)"] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" -groups = ["dev", "test"] +python-versions = ">=3.9" files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pre-commit" @@ -1460,7 +1467,6 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -1475,14 +1481,13 @@ virtualenv = ">=20.10.0" [[package]] name = "proto-plus" -version = "1.26.1" +version = "1.27.0" description = "Beautiful, Pythonic protocol buffers" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ - {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, - {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, + {file = "proto_plus-1.27.0-py3-none-any.whl", hash = "sha256:1baa7f81cf0f8acb8bc1f6d085008ba4171eaf669629d1b6d1673b21ed1c0a82"}, + {file = "proto_plus-1.27.0.tar.gz", hash = "sha256:873af56dd0d7e91836aee871e5799e1c6f1bda86ac9a983e0bb9f0c266a568c4"}, ] [package.dependencies] @@ -1493,21 +1498,21 @@ testing = ["google-api-core (>=1.31.5)"] [[package]] name = "protobuf" -version = "6.30.2" +version = "6.33.4" description = "" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ - {file = "protobuf-6.30.2-cp310-abi3-win32.whl", hash = "sha256:b12ef7df7b9329886e66404bef5e9ce6a26b54069d7f7436a0853ccdeb91c103"}, - {file = "protobuf-6.30.2-cp310-abi3-win_amd64.whl", hash = "sha256:7653c99774f73fe6b9301b87da52af0e69783a2e371e8b599b3e9cb4da4b12b9"}, - {file = "protobuf-6.30.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:0eb523c550a66a09a0c20f86dd554afbf4d32b02af34ae53d93268c1f73bc65b"}, - {file = "protobuf-6.30.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:50f32cc9fd9cb09c783ebc275611b4f19dfdfb68d1ee55d2f0c7fa040df96815"}, - {file = "protobuf-6.30.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4f6c687ae8efae6cf6093389a596548214467778146b7245e886f35e1485315d"}, - {file = "protobuf-6.30.2-cp39-cp39-win32.whl", hash = "sha256:524afedc03b31b15586ca7f64d877a98b184f007180ce25183d1a5cb230ee72b"}, - {file = "protobuf-6.30.2-cp39-cp39-win_amd64.whl", hash = "sha256:acec579c39c88bd8fbbacab1b8052c793efe83a0a5bd99db4a31423a25c0a0e2"}, - {file = "protobuf-6.30.2-py3-none-any.whl", hash = "sha256:ae86b030e69a98e08c77beab574cbcb9fff6d031d57209f574a5aea1445f4b51"}, - {file = "protobuf-6.30.2.tar.gz", hash = "sha256:35c859ae076d8c56054c25b59e5e59638d86545ed6e2b6efac6be0b6ea3ba048"}, + {file = "protobuf-6.33.4-cp310-abi3-win32.whl", hash = "sha256:918966612c8232fc6c24c78e1cd89784307f5814ad7506c308ee3cf86662850d"}, + {file = "protobuf-6.33.4-cp310-abi3-win_amd64.whl", hash = "sha256:8f11ffae31ec67fc2554c2ef891dcb561dae9a2a3ed941f9e134c2db06657dbc"}, + {file = "protobuf-6.33.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2fe67f6c014c84f655ee06f6f66213f9254b3a8b6bda6cda0ccd4232c73c06f0"}, + {file = "protobuf-6.33.4-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:757c978f82e74d75cba88eddec479df9b99a42b31193313b75e492c06a51764e"}, + {file = "protobuf-6.33.4-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c7c64f259c618f0bef7bee042075e390debbf9682334be2b67408ec7c1c09ee6"}, + {file = "protobuf-6.33.4-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:3df850c2f8db9934de4cf8f9152f8dc2558f49f298f37f90c517e8e5c84c30e9"}, + {file = "protobuf-6.33.4-cp39-cp39-win32.whl", hash = "sha256:955478a89559fa4568f5a81dce77260eabc5c686f9e8366219ebd30debf06aa6"}, + {file = "protobuf-6.33.4-cp39-cp39-win_amd64.whl", hash = "sha256:0f12ddbf96912690c3582f9dffb55530ef32015ad8e678cd494312bd78314c4f"}, + {file = "protobuf-6.33.4-py3-none-any.whl", hash = "sha256:1fe3730068fcf2e595816a6c34fe66eeedd37d51d0400b72fabc848811fdc1bc"}, + {file = "protobuf-6.33.4.tar.gz", hash = "sha256:dc2e61bca3b10470c1912d166fe0af67bfc20eb55971dcef8dfa48ce14f0ed91"}, ] [[package]] @@ -1516,7 +1521,6 @@ version = "6.1.1" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["dev"] files = [ {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, @@ -1547,7 +1551,6 @@ version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, @@ -1559,7 +1562,6 @@ version = "0.4.2" description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, @@ -1570,37 +1572,40 @@ pyasn1 = ">=0.6.1,<0.7.0" [[package]] name = "pycairo" -version = "1.27.0" +version = "1.29.0" description = "Python interface for cairo" optional = false -python-versions = ">=3.9" -groups = ["dev"] -markers = "sys_platform == \"linux\"" -files = [ - {file = "pycairo-1.27.0-cp310-cp310-win32.whl", hash = "sha256:e20f431244634cf244ab6b4c3a2e540e65746eed1324573cf291981c3e65fc05"}, - {file = "pycairo-1.27.0-cp310-cp310-win_amd64.whl", hash = "sha256:03bf570e3919901572987bc69237b648fe0de242439980be3e606b396e3318c9"}, - {file = "pycairo-1.27.0-cp311-cp311-win32.whl", hash = "sha256:9a9b79f92a434dae65c34c830bb9abdbd92654195e73d52663cbe45af1ad14b2"}, - {file = "pycairo-1.27.0-cp311-cp311-win_amd64.whl", hash = "sha256:d40a6d80b15dacb3672dc454df4bc4ab3988c6b3f36353b24a255dc59a1c8aea"}, - {file = "pycairo-1.27.0-cp312-cp312-win32.whl", hash = "sha256:e2239b9bb6c05edae5f3be97128e85147a155465e644f4d98ea0ceac7afc04ee"}, - {file = "pycairo-1.27.0-cp312-cp312-win_amd64.whl", hash = "sha256:27cb4d3a80e3b9990af552818515a8e466e0317063a6e61585533f1a86f1b7d5"}, - {file = "pycairo-1.27.0-cp313-cp313-win32.whl", hash = "sha256:01505c138a313df2469f812405963532fc2511fb9bca9bdc8e0ab94c55d1ced8"}, - {file = "pycairo-1.27.0-cp313-cp313-win_amd64.whl", hash = "sha256:b0349d744c068b6644ae23da6ada111c8a8a7e323b56cbce3707cba5bdb474cc"}, - {file = "pycairo-1.27.0-cp39-cp39-win32.whl", hash = "sha256:f9ca8430751f1fdcd3f072377560c9e15608b9a42d61375469db853566993c9b"}, - {file = "pycairo-1.27.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b1321652a6e27c4de3069709b1cae22aed2707fd8c5e889c04a95669228af2a"}, - {file = "pycairo-1.27.0.tar.gz", hash = "sha256:5cb21e7a00a2afcafea7f14390235be33497a2cce53a98a19389492a60628430"}, +python-versions = ">=3.10" +files = [ + {file = "pycairo-1.29.0-cp310-cp310-win32.whl", hash = "sha256:96c67e6caba72afd285c2372806a0175b1aa2f4537aa88fb4d9802d726effcd1"}, + {file = "pycairo-1.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:65bddd944aee9f7d7d72821b1c87e97593856617c2820a78d589d66aa8afbd08"}, + {file = "pycairo-1.29.0-cp310-cp310-win_arm64.whl", hash = "sha256:15b36aea699e2ff215cb6a21501223246032e572a3a10858366acdd69c81a1c8"}, + {file = "pycairo-1.29.0-cp311-cp311-win32.whl", hash = "sha256:12757ebfb304b645861283c20585c9204c3430671fad925419cba04844d6dfed"}, + {file = "pycairo-1.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:3391532db03f9601c1cee9ebfa15b7d1db183c6020f3e75c1348cee16825934f"}, + {file = "pycairo-1.29.0-cp311-cp311-win_arm64.whl", hash = "sha256:b69be8bb65c46b680771dc6a1a422b1cdd0cffb17be548f223e8cbbb6205567c"}, + {file = "pycairo-1.29.0-cp312-cp312-win32.whl", hash = "sha256:91bcd7b5835764c616a615d9948a9afea29237b34d2ed013526807c3d79bb1d0"}, + {file = "pycairo-1.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:3f01c3b5e49ef9411fff6bc7db1e765f542dc1c9cfed4542958a5afa3a8b8e76"}, + {file = "pycairo-1.29.0-cp312-cp312-win_arm64.whl", hash = "sha256:eafe3d2076f3533535ad4a361fa0754e0ee66b90e548a3a0f558fed00b1248f2"}, + {file = "pycairo-1.29.0-cp313-cp313-win32.whl", hash = "sha256:3eb382a4141591807073274522f7aecab9e8fa2f14feafd11ac03a13a58141d7"}, + {file = "pycairo-1.29.0-cp313-cp313-win_amd64.whl", hash = "sha256:91114e4b3fbf4287c2b0788f83e1f566ce031bda49cf1c3c3c19c3e986e95c38"}, + {file = "pycairo-1.29.0-cp313-cp313-win_arm64.whl", hash = "sha256:09b7f69a5ff6881e151354ea092137b97b0b1f0b2ab4eb81c92a02cc4a08e335"}, + {file = "pycairo-1.29.0-cp314-cp314-win32.whl", hash = "sha256:69e2a7968a3fbb839736257bae153f547bca787113cc8d21e9e08ca4526e0b6b"}, + {file = "pycairo-1.29.0-cp314-cp314-win_amd64.whl", hash = "sha256:e91243437a21cc4c67c401eff4433eadc45745275fa3ade1a0d877e50ffb90da"}, + {file = "pycairo-1.29.0-cp314-cp314-win_arm64.whl", hash = "sha256:b72200ea0e5f73ae4c788cd2028a750062221385eb0e6d8f1ecc714d0b4fdf82"}, + {file = "pycairo-1.29.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5e45fce6185f553e79e4ef1722b8e98e6cde9900dbc48cb2637a9ccba86f627a"}, + {file = "pycairo-1.29.0-cp314-cp314t-win_arm64.whl", hash = "sha256:caba0837a4b40d47c8dfb0f24cccc12c7831e3dd450837f2a356c75f21ce5a15"}, + {file = "pycairo-1.29.0.tar.gz", hash = "sha256:f3f7fde97325cae80224c09f12564ef58d0d0f655da0e3b040f5807bd5bd3142"}, ] [[package]] name = "pycparser" -version = "2.22" +version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] -markers = "platform_python_implementation != \"PyPy\"" files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, + {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, + {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] [[package]] @@ -1609,7 +1614,6 @@ version = "2.7.1" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, @@ -1629,7 +1633,6 @@ version = "2.18.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, @@ -1721,7 +1724,6 @@ version = "0.3.1" description = "Library for generating identicons. Port of Sigil (https://github.com/cupcake/sigil) with enhancements." optional = false python-versions = "*" -groups = ["main"] files = [ {file = "pydenticon-0.3.1.tar.gz", hash = "sha256:2ef363cdd6f4f0193ce62257486027e36884570f6140bbde51de72df321b77f1"}, ] @@ -1729,14 +1731,26 @@ files = [ [package.dependencies] Pillow = "*" +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pygobject" version = "3.50.0" description = "Python bindings for GObject Introspection" optional = false python-versions = "<4.0,>=3.9" -groups = ["dev"] -markers = "sys_platform == \"linux\"" files = [ {file = "pygobject-3.50.0.tar.gz", hash = "sha256:4500ad3dbf331773d8dedf7212544c999a76fc96b63a91b3dcac1e5925a1d103"}, ] @@ -1746,32 +1760,31 @@ pycairo = ">=1.16" [[package]] name = "pyinstaller" -version = "6.12.0" +version = "6.18.0" description = "PyInstaller bundles a Python application and all its dependencies into a single package." optional = false -python-versions = "<3.14,>=3.8" -groups = ["dev"] +python-versions = "<3.15,>=3.8" files = [ - {file = "pyinstaller-6.12.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:68f1e4cecf88a6272063977fa2a2c69ad37cf568e5901769d7206d0314c74f47"}, - {file = "pyinstaller-6.12.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fea76fc9b55ffa730fcf90beb897cce4399938460b0b6f40507fbebfc752c753"}, - {file = "pyinstaller-6.12.0-py3-none-manylinux2014_i686.whl", hash = "sha256:dac8a27988dbc33cdc34f2046803258bc3f6829de24de52745a5daa22bdba0f1"}, - {file = "pyinstaller-6.12.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:83c7f3bde9871b4a6aa71c66a96e8ba5c21668ce711ed97f510b9382d10aac6c"}, - {file = "pyinstaller-6.12.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:a69818815c6e0711c727edc30680cb1f81c691b59de35db81a2d9e0ae26a9ef1"}, - {file = "pyinstaller-6.12.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a2abf5fde31a8b38b6df7939bcef8ac1d0c51e97e25317ce3555cd675259750f"}, - {file = "pyinstaller-6.12.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:8e92e9873a616547bbabbb5a3a9843d5f2ab40c3d8b26810acdf0fe257bee4cf"}, - {file = "pyinstaller-6.12.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:aefe502d55c9cf6aeaed7feba80b5f8491ce43f8f2b5fe2d9aadca3ee5a05bc4"}, - {file = "pyinstaller-6.12.0-py3-none-win32.whl", hash = "sha256:138856a5a503bb69c066377e0a22671b0db063e9cc14d5cf5c798a53561200d3"}, - {file = "pyinstaller-6.12.0-py3-none-win_amd64.whl", hash = "sha256:0e62d3906309248409f215b386f33afec845214e69cc0f296b93222b26a88f43"}, - {file = "pyinstaller-6.12.0-py3-none-win_arm64.whl", hash = "sha256:0c271896a3a168f4f91827145702543db9c5427f4c7372a6df8c75925a3ac18a"}, - {file = "pyinstaller-6.12.0.tar.gz", hash = "sha256:1834797be48ce1b26015af68bdeb3c61a6c7500136f04e0fc65e468115dec777"}, + {file = "pyinstaller-6.18.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:cb7aa5a71bfa7c0af17a4a4e21855663c89e4bd7c40f1d337c8370636d8847c3"}, + {file = "pyinstaller-6.18.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:07785459b3bf8a48889eac0b4d0667ade84aef8930ce030bc7cbb32f41283b33"}, + {file = "pyinstaller-6.18.0-py3-none-manylinux2014_i686.whl", hash = "sha256:f998675b7ccb2dabbb1dc2d6f18af61d55428ad6d38e6c4d700417411b697d37"}, + {file = "pyinstaller-6.18.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:779817a0cf69604cddcdb5be1fd4959dc2ce048d6355c73e5da97884df2f3387"}, + {file = "pyinstaller-6.18.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:31b5d109f8405be0b7cddcede43e7b074792bc9a5bbd54ec000a3e779183c2af"}, + {file = "pyinstaller-6.18.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4328c9837f1aef4fe1a127d4ff1b09a12ce53c827ce87c94117628b0e1fd098b"}, + {file = "pyinstaller-6.18.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:3638fc81eb948e5e5eab1d4ad8f216e3fec6d4a350648304f0adb227b746ee5e"}, + {file = "pyinstaller-6.18.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe59da34269e637f97fd3c43024f764586fc319141d245ff1a2e9af1036aa3"}, + {file = "pyinstaller-6.18.0-py3-none-win32.whl", hash = "sha256:496205e4fa92ec944f9696eb597962a83aef4d4c3479abfab83d730e1edf016b"}, + {file = "pyinstaller-6.18.0-py3-none-win_amd64.whl", hash = "sha256:976fabd90ecfbda47571c87055ad73413ec615ff7dea35e12a4304174de78de9"}, + {file = "pyinstaller-6.18.0-py3-none-win_arm64.whl", hash = "sha256:dba4b70e3c9ba09aab51152c72a08e58a751851548f77ad35944d32a300c8381"}, + {file = "pyinstaller-6.18.0.tar.gz", hash = "sha256:cdc507542783511cad4856fce582fdc37e9f29665ca596889c663c83ec8c6ec9"}, ] [package.dependencies] altgraph = "*" macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} packaging = ">=22.0" -pefile = {version = ">=2022.5.30,<2024.8.26 || >2024.8.26", markers = "sys_platform == \"win32\""} -pyinstaller-hooks-contrib = ">=2025.1" +pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} +pyinstaller-hooks-contrib = ">=2025.9" pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""} setuptools = ">=42.0.0" @@ -1781,14 +1794,13 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2025.2" +version = "2025.11" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "pyinstaller_hooks_contrib-2025.2-py3-none-any.whl", hash = "sha256:0b2bc7697075de5eb071ff13ef4a156d3beae6c19c7cbdcd70f37978d2013e30"}, - {file = "pyinstaller_hooks_contrib-2025.2.tar.gz", hash = "sha256:ccdd41bc30290f725f3e48f4a39985d11855af81d614d167e3021e303acb9102"}, + {file = "pyinstaller_hooks_contrib-2025.11-py3-none-any.whl", hash = "sha256:777e163e2942474aa41a8e6d31ac1635292d63422c3646c176d584d04d971c34"}, + {file = "pyinstaller_hooks_contrib-2025.11.tar.gz", hash = "sha256:dfe18632e06655fa88d218e0d768fd753e1886465c12a6d4bce04f1aaeec917d"}, ] [package.dependencies] @@ -1801,7 +1813,6 @@ version = "3.1.0" description = "python code static checker" optional = false python-versions = ">=3.8.0" -groups = ["dev"] files = [ {file = "pylint-3.1.0-py3-none-any.whl", hash = "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74"}, {file = "pylint-3.1.0.tar.gz", hash = "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"}, @@ -1813,7 +1824,7 @@ colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, - {version = ">=0.3.6", markers = "python_version == \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" mccabe = ">=0.6,<0.8" @@ -1831,7 +1842,6 @@ version = "2.9.0" description = "Python One Time Password Library" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pyotp-2.9.0-py3-none-any.whl", hash = "sha256:81c2e5865b8ac55e825b0358e496e1d9387c811e85bb40e71a3b29b288963612"}, {file = "pyotp-2.9.0.tar.gz", hash = "sha256:346b6642e0dbdde3b4ff5a930b664ca82abfa116356ed48cc42c7d6590d36f63"}, @@ -1842,14 +1852,13 @@ test = ["coverage", "mypy", "ruff", "wheel"] [[package]] name = "pyparsing" -version = "3.2.3" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" +version = "3.3.1" +description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ - {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, - {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, + {file = "pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82"}, + {file = "pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c"}, ] [package.extras] @@ -1857,14 +1866,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyperclip" -version = "1.9.0" +version = "1.11.0" description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)" optional = false python-versions = "*" -groups = ["dev"] -markers = "sys_platform == \"linux\"" files = [ - {file = "pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310"}, + {file = "pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273"}, + {file = "pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6"}, ] [[package]] @@ -1873,7 +1881,6 @@ version = "0.20220715.0" description = "Pure Python library for saving and loading PNG images" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c"}, {file = "pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"}, @@ -1885,7 +1892,6 @@ version = "1.3.3" description = "A fully customizable toast notification library for PyQt and PySide" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pyqt-toast-notification-1.3.3.tar.gz", hash = "sha256:595dbf4b9edee77329e2514255d9a9415ff5d70708c50790db0fee207d323a29"}, {file = "pyqt_toast_notification-1.3.3-py3-none-any.whl", hash = "sha256:65ba011f536aa728f0b3ad3a452ab2b578a17f6e0e3468d6f1dc97f5b1cd4d15"}, @@ -1900,7 +1906,6 @@ version = "6.7.3" description = "Python bindings for the Qt cross-platform application and UI framework" optional = false python-versions = "<3.13,>=3.9" -groups = ["main"] files = [ {file = "PySide6-6.7.3-cp39-abi3-macosx_11_0_universal2.whl", hash = "sha256:1c21c4cf6cdd29bd13bbd7a2514756a19188eab992b92af03e64bf06a9b33d5b"}, {file = "PySide6-6.7.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a21480cc746358f70768975fcc452322f03b3c3622625bfb1743b40ce4e24beb"}, @@ -1919,7 +1924,6 @@ version = "6.7.3" description = "Python bindings for the Qt cross-platform application and UI framework (Addons)" optional = false python-versions = "<3.13,>=3.9" -groups = ["main"] files = [ {file = "PySide6_Addons-6.7.3-cp39-abi3-macosx_11_0_universal2.whl", hash = "sha256:3174cb3a373c09c98740b452e8e8f4945d64cfa18ed8d43964111d570f0dc647"}, {file = "PySide6_Addons-6.7.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:bde1eb03dbffd089b50cd445847aaecaf4056cea84c49ea592d00f84f247251e"}, @@ -1937,7 +1941,6 @@ version = "6.7.3" description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)" optional = false python-versions = "<3.13,>=3.9" -groups = ["main"] files = [ {file = "PySide6_Essentials-6.7.3-cp39-abi3-macosx_11_0_universal2.whl", hash = "sha256:f9e08a4e9e7dc7b5ab72fde20abce8c97df7af1b802d9743f098f577dfe1f649"}, {file = "PySide6_Essentials-6.7.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cda6fd26aead48f32e57f044d18aa75dc39265b49d7957f515ce7ac3989e7029"}, @@ -1950,26 +1953,26 @@ shiboken6 = "6.7.3" [[package]] name = "pytest" -version = "8.3.5" +version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.8" -groups = ["dev", "test"] +python-versions = ">=3.9" files = [ - {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, - {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" pluggy = ">=1.5,<2" +pygments = ">=2.7.2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" @@ -1977,7 +1980,6 @@ version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" -groups = ["test"] files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, @@ -1996,7 +1998,6 @@ version = "4.1.1" description = "pytest plugin for generating HTML reports" optional = false python-versions = ">=3.8" -groups = ["test"] files = [ {file = "pytest_html-4.1.1-py3-none-any.whl", hash = "sha256:c8152cea03bd4e9bee6d525573b67bbc6622967b72b9628dda0ea3e2a0b5dd71"}, {file = "pytest_html-4.1.1.tar.gz", hash = "sha256:70a01e8ae5800f4a074b56a4cb1025c8f4f9b038bba5fe31e3c98eb996686f07"}, @@ -2017,7 +2018,6 @@ version = "3.1.1" description = "pytest plugin for test session metadata" optional = false python-versions = ">=3.8" -groups = ["test"] files = [ {file = "pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b"}, {file = "pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8"}, @@ -2031,14 +2031,13 @@ test = ["black (>=22.1.0)", "flake8 (>=4.0.1)", "pre-commit (>=2.17.0)", "tox (> [[package]] name = "pytest-mock" -version = "3.14.0" +version = "3.15.1" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false -python-versions = ">=3.8" -groups = ["test"] +python-versions = ">=3.9" files = [ - {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, - {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, + {file = "pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d"}, + {file = "pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f"}, ] [package.dependencies] @@ -2053,7 +2052,6 @@ version = "1.3.0" description = "pytest plugin to run your tests in a specific order" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest_order-1.3.0-py3-none-any.whl", hash = "sha256:2cd562a21380345dd8d5774aa5fd38b7849b6ee7397ca5f6999bbe6e89f07f6e"}, {file = "pytest_order-1.3.0.tar.gz", hash = "sha256:51608fec3d3ee9c0adaea94daa124a5c4c1d2bb99b00269f098f414307f23dde"}, @@ -2064,34 +2062,33 @@ pytest = {version = ">=6.2.4", markers = "python_version >= \"3.10\""} [[package]] name = "pytest-qt" -version = "4.4.0" +version = "4.5.0" description = "pytest support for PyQt and PySide applications" optional = false -python-versions = ">=3.8" -groups = ["test"] +python-versions = ">=3.9" files = [ - {file = "pytest-qt-4.4.0.tar.gz", hash = "sha256:76896142a940a4285339008d6928a36d4be74afec7e634577e842c9cc5c56844"}, - {file = "pytest_qt-4.4.0-py3-none-any.whl", hash = "sha256:001ed2f8641764b394cf286dc8a4203e40eaf9fff75bf0bfe5103f7f8d0c591d"}, + {file = "pytest_qt-4.5.0-py3-none-any.whl", hash = "sha256:ed21ea9b861247f7d18090a26bfbda8fb51d7a8a7b6f776157426ff2ccf26eff"}, + {file = "pytest_qt-4.5.0.tar.gz", hash = "sha256:51620e01c488f065d2036425cbc1cbcf8a6972295105fd285321eb47e66a319f"}, ] [package.dependencies] pluggy = ">=1.1" pytest = "*" +typing_extensions = "*" [package.extras] dev = ["pre-commit", "tox"] -doc = ["sphinx", "sphinx-rtd-theme"] +doc = ["sphinx", "sphinx_rtd_theme"] [[package]] name = "pytest-xdist" -version = "3.6.1" +version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, - {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, + {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, + {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, ] [package.dependencies] @@ -2105,14 +2102,13 @@ testing = ["filelock"] [[package]] name = "python-dotenv" -version = "1.1.0" +version = "1.2.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ - {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, - {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, + {file = "python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61"}, + {file = "python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6"}, ] [package.extras] @@ -2124,8 +2120,6 @@ version = "1.19.0" description = "" optional = false python-versions = ">=3.6" -groups = ["dev"] -markers = "sys_platform == \"linux\"" files = [ {file = "python3_pyatspi-1.19.0-py3-none-any.whl", hash = "sha256:cc0d5436bf925199c28f421279dc3e428a2aeba92bcb4d8e63013eb01b205dfc"}, {file = "python3_pyatspi-1.19.0.tar.gz", hash = "sha256:c6f2d8ccded46b25c161a42b106053d33c0e23da53a63f346aec4a97e6e5aafd"}, @@ -2137,8 +2131,6 @@ version = "0.15" description = "Python3 X Library" optional = false python-versions = "*" -groups = ["dev"] -markers = "sys_platform == \"linux\"" files = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] @@ -2149,8 +2141,6 @@ version = "306" description = "Python for Window Extensions" optional = false python-versions = "*" -groups = ["dev"] -markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, @@ -2174,8 +2164,6 @@ version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] -markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, @@ -2183,65 +2171,84 @@ files = [ [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +files = [ + {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, + {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, + {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, + {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] [[package]] @@ -2250,7 +2257,6 @@ version = "7.4.2" description = "QR Code image generator" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "qrcode-7.4.2-py3-none-any.whl", hash = "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a"}, {file = "qrcode-7.4.2.tar.gz", hash = "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845"}, @@ -2274,7 +2280,6 @@ version = "2.4.3" description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1"}, {file = "qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb"}, @@ -2288,106 +2293,94 @@ test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] [[package]] name = "rapidfuzz" -version = "3.13.0" +version = "3.14.3" description = "rapid fuzzy string matching" optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "rapidfuzz-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aafc42a1dc5e1beeba52cd83baa41372228d6d8266f6d803c16dbabbcc156255"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85c9a131a44a95f9cac2eb6e65531db014e09d89c4f18c7b1fa54979cb9ff1f3"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d7cec4242d30dd521ef91c0df872e14449d1dffc2a6990ede33943b0dae56c3"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e297c09972698c95649e89121e3550cee761ca3640cd005e24aaa2619175464e"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef0f5f03f61b0e5a57b1df7beafd83df993fd5811a09871bad6038d08e526d0d"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8cf5f7cd6e4d5eb272baf6a54e182b2c237548d048e2882258336533f3f02b7"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9256218ac8f1a957806ec2fb9a6ddfc6c32ea937c0429e88cf16362a20ed8602"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1bdd2e6d0c5f9706ef7595773a81ca2b40f3b33fd7f9840b726fb00c6c4eb2e"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5280be8fd7e2bee5822e254fe0a5763aa0ad57054b85a32a3d9970e9b09bbcbf"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd742c03885db1fce798a1cd87a20f47f144ccf26d75d52feb6f2bae3d57af05"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5435fcac94c9ecf0504bf88a8a60c55482c32e18e108d6079a0089c47f3f8cf6"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:93a755266856599be4ab6346273f192acde3102d7aa0735e2f48b456397a041f"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-win32.whl", hash = "sha256:3abe6a4e8eb4cfc4cda04dd650a2dc6d2934cbdeda5def7e6fd1c20f6e7d2a0b"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:e8ddb58961401da7d6f55f185512c0d6bd24f529a637078d41dd8ffa5a49c107"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-win_arm64.whl", hash = "sha256:c523620d14ebd03a8d473c89e05fa1ae152821920c3ff78b839218ff69e19ca3"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d395a5cad0c09c7f096433e5fd4224d83b53298d53499945a9b0e5a971a84f3a"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7b3eda607a019169f7187328a8d1648fb9a90265087f6903d7ee3a8eee01805"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98e0bfa602e1942d542de077baf15d658bd9d5dcfe9b762aff791724c1c38b70"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bef86df6d59667d9655905b02770a0c776d2853971c0773767d5ef8077acd624"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fedd316c165beed6307bf754dee54d3faca2c47e1f3bcbd67595001dfa11e969"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5158da7f2ec02a930be13bac53bb5903527c073c90ee37804090614cab83c29e"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b6f913ee4618ddb6d6f3e387b76e8ec2fc5efee313a128809fbd44e65c2bbb2"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d25fdbce6459ccbbbf23b4b044f56fbd1158b97ac50994eaae2a1c0baae78301"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25343ccc589a4579fbde832e6a1e27258bfdd7f2eb0f28cb836d6694ab8591fc"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a9ad1f37894e3ffb76bbab76256e8a8b789657183870be11aa64e306bb5228fd"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5dc71ef23845bb6b62d194c39a97bb30ff171389c9812d83030c1199f319098c"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b7f4c65facdb94f44be759bbd9b6dda1fa54d0d6169cdf1a209a5ab97d311a75"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-win32.whl", hash = "sha256:b5104b62711565e0ff6deab2a8f5dbf1fbe333c5155abe26d2cfd6f1849b6c87"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:9093cdeb926deb32a4887ebe6910f57fbcdbc9fbfa52252c10b56ef2efb0289f"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:f70f646751b6aa9d05be1fb40372f006cc89d6aad54e9d79ae97bd1f5fce5203"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a1a6a906ba62f2556372282b1ef37b26bca67e3d2ea957277cfcefc6275cca7"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fd0975e015b05c79a97f38883a11236f5a24cca83aa992bd2558ceaa5652b26"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d4e13593d298c50c4f94ce453f757b4b398af3fa0fd2fde693c3e51195b7f69"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed6f416bda1c9133000009d84d9409823eb2358df0950231cc936e4bf784eb97"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dc82b6ed01acb536b94a43996a94471a218f4d89f3fdd9185ab496de4b2a981"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9d824de871daa6e443b39ff495a884931970d567eb0dfa213d234337343835f"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d18228a2390375cf45726ce1af9d36ff3dc1f11dce9775eae1f1b13ac6ec50f"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5fe634c9482ec5d4a6692afb8c45d370ae86755e5f57aa6c50bfe4ca2bdd87"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:694eb531889f71022b2be86f625a4209c4049e74be9ca836919b9e395d5e33b3"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11b47b40650e06147dee5e51a9c9ad73bb7b86968b6f7d30e503b9f8dd1292db"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:98b8107ff14f5af0243f27d236bcc6e1ef8e7e3b3c25df114e91e3a99572da73"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b836f486dba0aceb2551e838ff3f514a38ee72b015364f739e526d720fdb823a"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-win32.whl", hash = "sha256:4671ee300d1818d7bdfd8fa0608580d7778ba701817216f0c17fb29e6b972514"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e2065f68fb1d0bf65adc289c1bdc45ba7e464e406b319d67bb54441a1b9da9e"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:65cc97c2fc2c2fe23586599686f3b1ceeedeca8e598cfcc1b7e56dc8ca7e2aa7"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:09e908064d3684c541d312bd4c7b05acb99a2c764f6231bd507d4b4b65226c23"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:57c390336cb50d5d3bfb0cfe1467478a15733703af61f6dffb14b1cd312a6fae"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0da54aa8547b3c2c188db3d1c7eb4d1bb6dd80baa8cdaeaec3d1da3346ec9caa"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df8e8c21e67afb9d7fbe18f42c6111fe155e801ab103c81109a61312927cc611"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:461fd13250a2adf8e90ca9a0e1e166515cbcaa5e9c3b1f37545cbbeff9e77f6b"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2b3dd5d206a12deca16870acc0d6e5036abeb70e3cad6549c294eff15591527"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1343d745fbf4688e412d8f398c6e6d6f269db99a54456873f232ba2e7aeb4939"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b1b065f370d54551dcc785c6f9eeb5bd517ae14c983d2784c064b3aa525896df"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:11b125d8edd67e767b2295eac6eb9afe0b1cdc82ea3d4b9257da4b8e06077798"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c33f9c841630b2bb7e69a3fb5c84a854075bb812c47620978bddc591f764da3d"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae4574cb66cf1e85d32bb7e9ec45af5409c5b3970b7ceb8dea90168024127566"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e05752418b24bbd411841b256344c26f57da1148c5509e34ea39c7eb5099ab72"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-win32.whl", hash = "sha256:0e1d08cb884805a543f2de1f6744069495ef527e279e05370dd7c83416af83f8"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9a7c6232be5f809cd39da30ee5d24e6cadd919831e6020ec6c2391f4c3bc9264"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:3f32f15bacd1838c929b35c84b43618481e1b3d7a61b5ed2db0291b70ae88b53"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc64da907114d7a18b5e589057e3acaf2fec723d31c49e13fedf043592a3f6a7"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d9d7f84c8e992a8dbe5a3fdbea73d733da39bf464e62c912ac3ceba9c0cff93"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a79a2f07786a2070669b4b8e45bd96a01c788e7a3c218f531f3947878e0f956"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f338e71c45b69a482de8b11bf4a029993230760120c8c6e7c9b71760b6825a1"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb40ca8ddfcd4edd07b0713a860be32bdf632687f656963bcbce84cea04b8d8"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48719f7dcf62dfb181063b60ee2d0a39d327fa8ad81b05e3e510680c44e1c078"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9327a4577f65fc3fb712e79f78233815b8a1c94433d0c2c9f6bc5953018b3565"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:200030dfc0a1d5d6ac18e993c5097c870c97c41574e67f227300a1fb74457b1d"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cc269e74cad6043cb8a46d0ce580031ab642b5930562c2bb79aa7fbf9c858d26"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e62779c6371bd2b21dbd1fdce89eaec2d93fd98179d36f61130b489f62294a92"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f4797f821dc5d7c2b6fc818b89f8a3f37bcc900dd9e4369e6ebf1e525efce5db"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d21f188f6fe4fbf422e647ae9d5a68671d00218e187f91859c963d0738ccd88c"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-win32.whl", hash = "sha256:45dd4628dd9c21acc5c97627dad0bb791764feea81436fb6e0a06eef4c6dceaa"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:624a108122039af89ddda1a2b7ab2a11abe60c1521956f142f5d11bcd42ef138"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-win_arm64.whl", hash = "sha256:435071fd07a085ecbf4d28702a66fd2e676a03369ee497cc38bcb69a46bc77e2"}, - {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe5790a36d33a5d0a6a1f802aa42ecae282bf29ac6f7506d8e12510847b82a45"}, - {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:cdb33ee9f8a8e4742c6b268fa6bd739024f34651a06b26913381b1413ebe7590"}, - {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c99b76b93f7b495eee7dcb0d6a38fb3ce91e72e99d9f78faa5664a881cb2b7d"}, - {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6af42f2ede8b596a6aaf6d49fdee3066ca578f4856b85ab5c1e2145de367a12d"}, - {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c0efa73afbc5b265aca0d8a467ae2a3f40d6854cbe1481cb442a62b7bf23c99"}, - {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7ac21489de962a4e2fc1e8f0b0da4aa1adc6ab9512fd845563fecb4b4c52093a"}, - {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1ba007f4d35a45ee68656b2eb83b8715e11d0f90e5b9f02d615a8a321ff00c27"}, - {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d7a217310429b43be95b3b8ad7f8fc41aba341109dc91e978cd7c703f928c58f"}, - {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:558bf526bcd777de32b7885790a95a9548ffdcce68f704a81207be4a286c1095"}, - {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:202a87760f5145140d56153b193a797ae9338f7939eb16652dd7ff96f8faf64c"}, - {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcccc08f671646ccb1e413c773bb92e7bba789e3a1796fd49d23c12539fe2e4"}, - {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f219f1e3c3194d7a7de222f54450ce12bc907862ff9a8962d83061c1f923c86"}, - {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ccbd0e7ea1a216315f63ffdc7cd09c55f57851afc8fe59a74184cb7316c0598b"}, - {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50856f49a4016ef56edd10caabdaf3608993f9faf1e05c3c7f4beeac46bd12a"}, - {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fd05336db4d0b8348d7eaaf6fa3c517b11a56abaa5e89470ce1714e73e4aca7"}, - {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:573ad267eb9b3f6e9b04febce5de55d8538a87c56c64bf8fd2599a48dc9d8b77"}, - {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30fd1451f87ccb6c2f9d18f6caa483116bbb57b5a55d04d3ddbd7b86f5b14998"}, - {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6dd36d4916cf57ddb05286ed40b09d034ca5d4bca85c17be0cb6a21290597d9"}, - {file = "rapidfuzz-3.13.0.tar.gz", hash = "sha256:d2eaf3839e52cbcc0accbe9817a67b4b0fcf70aaeb229cfddc1c28061f9ce5d8"}, +python-versions = ">=3.10" +files = [ + {file = "rapidfuzz-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9fcd4d751a4fffa17aed1dde41647923c72c74af02459ad1222e3b0022da3a1"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ad73afb688b36864a8d9b7344a9cf6da186c471e5790cbf541a635ee0f457f2"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5fb2d978a601820d2cfd111e2c221a9a7bfdf84b41a3ccbb96ceef29f2f1ac7"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d83b8b712fa37e06d59f29a4b49e2e9e8635e908fbc21552fe4d1163db9d2a1"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:dc8c07801df5206b81ed6bd6c35cb520cf9b6c64b9b0d19d699f8633dc942897"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c71ce6d4231e5ef2e33caa952bfe671cb9fd42e2afb11952df9fad41d5c821f9"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0e38828d1381a0cceb8a4831212b2f673d46f5129a1897b0451c883eaf4a1747"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da2a007434323904719158e50f3076a4dadb176ce43df28ed14610c773cc9825"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-win32.whl", hash = "sha256:fce3152f94afcfd12f3dd8cf51e48fa606e3cb56719bccebe3b401f43d0714f9"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:37d3c653af15cd88592633e942f5407cb4c64184efab163c40fcebad05f25141"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:cc594bbcd3c62f647dfac66800f307beaee56b22aaba1c005e9c4c40ed733923"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dea2d113e260a5da0c4003e0a5e9fdf24a9dc2bb9eaa43abd030a1e46ce7837d"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e6c31a4aa68cfa75d7eede8b0ed24b9e458447db604c2db53f358be9843d81d3"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02821366d928e68ddcb567fed8723dad7ea3a979fada6283e6914d5858674850"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfe8df315ab4e6db4e1be72c5170f8e66021acde22cd2f9d04d2058a9fd8162e"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:769f31c60cd79420188fcdb3c823227fc4a6deb35cafec9d14045c7f6743acae"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54fa03062124e73086dae66a3451c553c1e20a39c077fd704dc7154092c34c63"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:834d1e818005ed0d4ae38f6b87b86fad9b0a74085467ece0727d20e15077c094"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:948b00e8476a91f510dd1ec07272efc7d78c275d83b630455559671d4e33b678"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-win32.whl", hash = "sha256:43d0305c36f504232f18ea04e55f2059bb89f169d3119c4ea96a0e15b59e2a91"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:ef6bf930b947bd0735c550683939a032090f1d688dfd8861d6b45307b96fd5c5"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:f3eb0ff3b75d6fdccd40b55e7414bb859a1cda77c52762c9c82b85569f5088e7"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:685c93ea961d135893b5984a5a9851637d23767feabe414ec974f43babbd8226"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57f878330c8d361b2ce76cebb8e3e1dc827293b6abf404e67d53260d27b5d941"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c5f545f454871e6af05753a0172849c82feaf0f521c5ca62ba09e1b382d6382"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:07aa0b5d8863e3151e05026a28e0d924accf0a7a3b605da978f0359bb804df43"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73b07566bc7e010e7b5bd490fb04bb312e820970180df6b5655e9e6224c137db"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6de00eb84c71476af7d3110cf25d8fe7c792d7f5fa86764ef0b4ca97e78ca3ed"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7843a1abf0091773a530636fdd2a49a41bcae22f9910b86b4f903e76ddc82dc"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-win32.whl", hash = "sha256:dea97ac3ca18cd3ba8f3d04b5c1fe4aa60e58e8d9b7793d3bd595fdb04128d7a"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:b5100fd6bcee4d27f28f4e0a1c6b5127bc8ba7c2a9959cad9eab0bf4a7ab3329"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:4e49c9e992bc5fc873bd0fff7ef16a4405130ec42f2ce3d2b735ba5d3d4eb70f"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbcb726064b12f356bf10fffdb6db4b6dce5390b23627c08652b3f6e49aa56ae"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1704fc70d214294e554a2421b473779bcdeef715881c5e927dc0f11e1692a0ff"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc65e72790ddfd310c2c8912b45106e3800fefe160b0c2ef4d6b6fec4e826457"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e38c1305cffae8472572a0584d4ffc2f130865586a81038ca3965301f7c97c"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:e195a77d06c03c98b3fc06b8a28576ba824392ce40de8c708f96ce04849a052e"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b7ef2f4b8583a744338a18f12c69693c194fb6777c0e9ada98cd4d9e8f09d10"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a2135b138bcdcb4c3742d417f215ac2d8c2b87bde15b0feede231ae95f09ec41"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33a325ed0e8e1aa20c3e75f8ab057a7b248fdea7843c2a19ade0008906c14af0"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-win32.whl", hash = "sha256:8383b6d0d92f6cd008f3c9216535be215a064b2cc890398a678b56e6d280cb63"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:e6b5e3036976f0fde888687d91be86d81f9ac5f7b02e218913c38285b756be6c"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:7ba009977601d8b0828bfac9a110b195b3e4e79b350dcfa48c11269a9f1918a0"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0a28add871425c2fe94358c6300bbeb0bc2ed828ca003420ac6825408f5a424"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010e12e2411a4854b0434f920e72b717c43f8ec48d57e7affe5c42ecfa05dd0e"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cfc3d57abd83c734d1714ec39c88a34dd69c85474918ebc21296f1e61eb5ca8"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89acb8cbb52904f763e5ac238083b9fc193bed8d1f03c80568b20e4cef43a519"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_31_armv7l.whl", hash = "sha256:7d9af908c2f371bfb9c985bd134e295038e3031e666e4b2ade1e7cb7f5af2f1a"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1f1925619627f8798f8c3a391d81071336942e5fe8467bc3c567f982e7ce2897"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:152555187360978119e98ce3e8263d70dd0c40c7541193fc302e9b7125cf8f58"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52619d25a09546b8db078981ca88939d72caa6b8701edd8b22e16482a38e799f"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-win32.whl", hash = "sha256:489ce98a895c98cad284f0a47960c3e264c724cb4cfd47a1430fa091c0c25204"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-win_amd64.whl", hash = "sha256:656e52b054d5b5c2524169240e50cfa080b04b1c613c5f90a2465e84888d6f15"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c7e40c0a0af02ad6e57e89f62bef8604f55a04ecae90b0ceeda591bbf5923317"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:442125473b247227d3f2de807a11da6c08ccf536572d1be943f8e262bae7e4ea"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ec0c8c0c3d4f97ced46b2e191e883f8c82dbbf6d5ebc1842366d7eff13cd5a6"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2dc37bc20272f388b8c3a4eba4febc6e77e50a8f450c472def4751e7678f55e4"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee362e7e79bae940a5e2b3f6d09c6554db6a4e301cc68343886c08be99844f1"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:4b39921df948388a863f0e267edf2c36302983459b021ab928d4b801cbe6a421"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:beda6aa9bc44d1d81242e7b291b446be352d3451f8217fcb068fc2933927d53b"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6a014ba09657abfcfeed64b7d09407acb29af436d7fc075b23a298a7e4a6b41c"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:32eeafa3abce138bb725550c0e228fc7eaeec7059aa8093d9cbbec2b58c2371a"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-win32.whl", hash = "sha256:adb44d996fc610c7da8c5048775b21db60dd63b1548f078e95858c05c86876a3"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:f3d15d8527e2b293e38ce6e437631af0708df29eafd7c9fc48210854c94472f9"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:576e4b9012a67e0bf54fccb69a7b6c94d4e86a9540a62f1a5144977359133583"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cec3c0da88562727dd5a5a364bd9efeb535400ff0bfb1443156dd139a1dd7b50"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d1fa009f8b1100e4880868137e7bf0501422898f7674f2adcd85d5a67f041296"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b86daa7419b5e8b180690efd1fdbac43ff19230803282521c5b5a9c83977655"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7bd1816db05d6c5ffb3a4df0a2b7b56fb8c81ef584d08e37058afa217da91b1"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:33da4bbaf44e9755b0ce192597f3bde7372fe2e381ab305f41b707a95ac57aa7"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3fecce764cf5a991ee2195a844196da840aba72029b2612f95ac68a8b74946bf"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ecd7453e02cf072258c3a6b8e930230d789d5d46cc849503729f9ce475d0e785"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea188aa00e9bcae8c8411f006a5f2f06c4607a02f24eab0d8dc58566aa911f35"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:7ccbf68100c170e9a0581accbe9291850936711548c6688ce3bfb897b8c589ad"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9ec02e62ae765a318d6de38df609c57fc6dacc65c0ed1fd489036834fd8a620c"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e805e52322ae29aa945baf7168b6c898120fbc16d2b8f940b658a5e9e3999253"}, + {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7cf174b52cb3ef5d49e45d0a1133b7e7d0ecf770ed01f97ae9962c5c91d97d23"}, + {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:442cba39957a008dfc5bdef21a9c3f4379e30ffb4e41b8555dbaf4887eca9300"}, + {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1faa0f8f76ba75fd7b142c984947c280ef6558b5067af2ae9b8729b0a0f99ede"}, + {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e6eefec45625c634926a9fd46c9e4f31118ac8f3156fff9494422cee45207e6"}, + {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56fefb4382bb12250f164250240b9dd7772e41c5c8ae976fd598a32292449cc5"}, + {file = "rapidfuzz-3.14.3.tar.gz", hash = "sha256:2491937177868bc4b1e469087601d53f925e8d270ccc21e07404b4b5814b7b5f"}, ] [package.extras] @@ -2399,7 +2392,6 @@ version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, @@ -2421,7 +2413,6 @@ version = "1.2.1" description = "A persistent cache for python requests" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "requests_cache-1.2.1-py3-none-any.whl", hash = "sha256:1285151cddf5331067baa82598afe2d47c7495a1334bfe7a7d329b43e9fd3603"}, {file = "requests_cache-1.2.1.tar.gz", hash = "sha256:68abc986fdc5b8d0911318fbb5f7c80eebcd4d01bfacc6685ecf8876052511d1"}, @@ -2452,7 +2443,6 @@ version = "2.0.0" description = "OAuthlib authentication support for Requests." optional = false python-versions = ">=3.4" -groups = ["dev"] files = [ {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, @@ -2467,30 +2457,28 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] [[package]] name = "rgb-lib" -version = "0.3.0a12" +version = "0.3.0b4" description = "RGB Lib Python language bindings." optional = false python-versions = ">=3.9.0" -groups = ["dev"] files = [ - {file = "rgb_lib-0.3.0a12-py3-none-macosx_12_0_arm64.whl", hash = "sha256:f4e5a3b2d1f4cb04f2466834e7d8985599df3982299c915ab10c79c9c790cb75"}, - {file = "rgb_lib-0.3.0a12-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:aba59d56bcf28af3da79d9674775c1bd2149841f97b4264b60e71939fa305ae8"}, - {file = "rgb_lib-0.3.0a12-py3-none-manylinux_2_34_aarch64.whl", hash = "sha256:3a24355bf16edd893445084126b7523b42b0a71322deaec53bb498d7192973d6"}, - {file = "rgb_lib-0.3.0a12-py3-none-manylinux_2_34_x86_64.whl", hash = "sha256:40463f14af98327ee02a27feb78b66d46fc5bdd1db5063ff6c9fbf3d790e65f2"}, - {file = "rgb_lib-0.3.0a12-py3-none-win_amd64.whl", hash = "sha256:96dc6942e61a94427e0dd0ff6101f2ea8bc965280e6ff11fcecc7d93207ca28f"}, - {file = "rgb_lib-0.3.0a12.tar.gz", hash = "sha256:ad9bd876f276f9d2e365c19fb16e09ddc1c3aa4d115fba2fad9817c984d120ee"}, + {file = "rgb_lib-0.3.0b4-py3-none-macosx_12_0_arm64.whl", hash = "sha256:dac156401abc7881e94eb5ebcbe3204b83b67669ddcc7fdfbc0ba476e91e60b2"}, + {file = "rgb_lib-0.3.0b4-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:20d3f552ca202c7564ed9e501f569c17e31d7dc8051fc8d076d11011062e1703"}, + {file = "rgb_lib-0.3.0b4-py3-none-manylinux_2_34_aarch64.whl", hash = "sha256:77d5ca45ca7d37abb92764468e2bc09de3dc1c7ad05fff7e38560bcfc2a7772f"}, + {file = "rgb_lib-0.3.0b4-py3-none-manylinux_2_34_x86_64.whl", hash = "sha256:44081536c8dd3ae1dc5175399e789f9748eade3e9e1cb7ed416066f66f675d80"}, + {file = "rgb_lib-0.3.0b4-py3-none-win_amd64.whl", hash = "sha256:3219cccce0b3d489b32e3f9122cc6867cf0efba266348ebd8955fa335c5d26a6"}, + {file = "rgb_lib-0.3.0b4.tar.gz", hash = "sha256:8ff21bc4c2fecf3f25561711d90a5c87049c5b5d60ae541b9b20d4bee99ed76e"}, ] [[package]] name = "rsa" -version = "4.9" +version = "4.9.1" description = "Pure-Python RSA implementation" optional = false -python-versions = ">=3.6,<4" -groups = ["dev"] +python-versions = "<4,>=3.6" files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, + {file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"}, + {file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"}, ] [package.dependencies] @@ -2498,15 +2486,13 @@ pyasn1 = ">=0.1.3" [[package]] name = "secretstorage" -version = "3.3.3" +version = "3.5.0" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false -python-versions = ">=3.6" -groups = ["main"] -markers = "sys_platform == \"linux\"" +python-versions = ">=3.10" files = [ - {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, - {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, + {file = "secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137"}, + {file = "secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be"}, ] [package.dependencies] @@ -2515,24 +2501,19 @@ jeepney = ">=0.6" [[package]] name = "setuptools" -version = "78.1.0" +version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = ">=3.7" files = [ - {file = "setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8"}, - {file = "setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54"}, + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "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) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "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) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "shiboken6" @@ -2540,7 +2521,6 @@ version = "6.7.3" description = "Python/C++ bindings helper module" optional = false python-versions = "<3.13,>=3.9" -groups = ["main"] files = [ {file = "shiboken6-6.7.3-cp39-abi3-macosx_11_0_universal2.whl", hash = "sha256:285fe3cf79be3135fe1ad1e2b9ff6db3a48698887425af6aa6ed7a05a9abc3d6"}, {file = "shiboken6-6.7.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f0852e5781de78be5b13c140ec4c7fb9734e2aaf2986eb2d6a224363e03efccc"}, @@ -2554,7 +2534,6 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2562,140 +2541,147 @@ files = [ [[package]] name = "tomli" -version = "2.2.1" +version = "2.4.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["dev", "test"] -markers = "python_version == \"3.10\"" -files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +files = [ + {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, + {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"}, + {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"}, + {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"}, + {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"}, + {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"}, + {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"}, + {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"}, + {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"}, + {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"}, + {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"}, + {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"}, + {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"}, + {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"}, + {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"}, + {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"}, + {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"}, + {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, + {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, ] [[package]] name = "tomlkit" -version = "0.13.2" +version = "0.14.0" description = "Style preserving TOML library" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, - {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, + {file = "tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680"}, + {file = "tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064"}, ] [[package]] name = "typing-extensions" -version = "4.13.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] +python-versions = ">=3.9" files = [ - {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, - {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] -markers = {dev = "python_version == \"3.10\""} [[package]] name = "uritemplate" -version = "4.1.1" +version = "4.2.0" description = "Implementation of RFC 6570 URI Templates" optional = false -python-versions = ">=3.6" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, - {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, + {file = "uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686"}, + {file = "uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e"}, ] [[package]] name = "url-normalize" -version = "2.2.0" +version = "2.2.1" description = "URL normalization for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "url_normalize-2.2.0-py3-none-any.whl", hash = "sha256:3fe387b62f5b66db94304bc703bf6d34de52aaa9590d4d1f1bbdf305a1430064"}, - {file = "url_normalize-2.2.0.tar.gz", hash = "sha256:0f0b7cc95a95d2d9b0c9a51d47a326559bc05bd1558accdada21bb0c9504de85"}, + {file = "url_normalize-2.2.1-py3-none-any.whl", hash = "sha256:3deb687587dc91f7b25c9ae5162ffc0f057ae85d22b1e15cf5698311247f567b"}, + {file = "url_normalize-2.2.1.tar.gz", hash = "sha256:74a540a3b6eba1d95bdc610c24f2c0141639f3ba903501e61a52a8730247ff37"}, ] [package.dependencies] idna = ">=3.3" [package.extras] -dev = ["mypy", "pre-commit", "pytest", "pytest-cov", "pytest-ruff", "pytest-socket", "ruff", "tox"] +dev = ["mypy", "pre-commit", "pytest", "pytest-cov", "pytest-socket", "ruff"] [[package]] name = "urllib3" -version = "2.4.0" +version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, - {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, + {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, + {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +zstd = ["backports-zstd (>=1.0.0)"] [[package]] name = "virtualenv" -version = "20.30.0" +version = "20.36.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6"}, - {file = "virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8"}, + {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, + {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, ] [package.dependencies] distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" +filelock = {version = ">=3.20.1,<4", markers = "python_version >= \"3.10\""} platformdirs = ">=3.9.1,<5" +typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "winsdk" @@ -2703,8 +2689,6 @@ version = "1.0.0b10" description = "Python bindings for the Windows SDK" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "sys_platform == \"win32\"" files = [ {file = "winsdk-1.0.0b10-cp310-cp310-win32.whl", hash = "sha256:90f75c67e166d588a045bcde0117a4631c705904f7af4ac42644479dcf0d8c52"}, {file = "winsdk-1.0.0b10-cp310-cp310-win_amd64.whl", hash = "sha256:c3be3fbf692b8888bac8c0712c490c080ab8976649ef01f9f6365947f4e5a8b1"}, @@ -2725,26 +2709,24 @@ files = [ [[package]] name = "zipp" -version = "3.21.0" +version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, - {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, + {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, + {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] -markers = {main = "python_version < \"3.12\""} [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +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 = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "2c8c40dab7561f11bae1e6a8c22aaf1141ba692a73ff8ebfa97889232b1f0e15" +content-hash = "415b4fab8ae0b6d3633a620d4e298447f10024c479bb913abd6cc464582a6378" diff --git a/pyproject.toml b/pyproject.toml index 3389a8e5..1c1243b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "iris-wallet-desktop" -version = "0.2.0" +version = "0.3.0" description = "" readme = "README.md" license = "" @@ -24,6 +24,7 @@ qrcode = "^7.4.2" pydenticon = "^0.3.0" # Development dependencies +setuptools = "68.0.0" [tool.poetry.dev-dependencies] pyqt-toast-notification = "^1.2.0" pre-commit = "^3.7.1" @@ -40,7 +41,7 @@ cryptography = "^43.0.0" asyncio = "^3.4.3" requests-cache = "^1.2.1" pytest-xdist = "^3.6.1" -rgb-lib = "0.3.0a12" +rgb-lib = "0.3.0b4" psutil = "^6.1.1" pyotp = "^2.9.0" pytest-order = "^1.3.0" diff --git a/src/assets/icons/testnet4-icon.png b/src/assets/icons/testnet4-icon.png new file mode 100644 index 00000000..7413cf5f Binary files /dev/null and b/src/assets/icons/testnet4-icon.png differ diff --git a/src/data/repository/setting_card_repository.py b/src/data/repository/setting_card_repository.py index 95cf1d2b..ed55fc0a 100644 --- a/src/data/repository/setting_card_repository.py +++ b/src/data/repository/setting_card_repository.py @@ -25,19 +25,23 @@ from src.utils.constant import BITCOIND_RPC_HOST_MAINNET from src.utils.constant import BITCOIND_RPC_HOST_REGTEST from src.utils.constant import BITCOIND_RPC_HOST_TESTNET +from src.utils.constant import BITCOIND_RPC_HOST_TESTNET4 from src.utils.constant import BITCOIND_RPC_PORT_MAINNET from src.utils.constant import BITCOIND_RPC_PORT_REGTEST from src.utils.constant import BITCOIND_RPC_PORT_TESTNET +from src.utils.constant import BITCOIND_RPC_PORT_TESTNET4 from src.utils.constant import FEE_RATE from src.utils.constant import INDEXER_URL_MAINNET from src.utils.constant import INDEXER_URL_REGTEST from src.utils.constant import INDEXER_URL_TESTNET +from src.utils.constant import INDEXER_URL_TESTNET4 from src.utils.constant import LN_INVOICE_EXPIRY_TIME from src.utils.constant import LN_INVOICE_EXPIRY_TIME_UNIT from src.utils.constant import MIN_CONFIRMATION from src.utils.constant import PROXY_ENDPOINT_MAINNET from src.utils.constant import PROXY_ENDPOINT_REGTEST from src.utils.constant import PROXY_ENDPOINT_TESTNET +from src.utils.constant import PROXY_ENDPOINT_TESTNET4 from src.utils.constant import SAVED_ANNOUNCE_ADDRESS from src.utils.constant import SAVED_ANNOUNCE_ALIAS from src.utils.constant import SAVED_BITCOIND_RPC_HOST @@ -199,6 +203,8 @@ def get_default_indexer_url() -> DefaultIndexerUrl: indexer_url = INDEXER_URL_MAINNET elif stored_network == NetworkEnumModel.TESTNET: indexer_url = INDEXER_URL_TESTNET + elif stored_network == NetworkEnumModel.TESTNET4: + indexer_url = INDEXER_URL_TESTNET4 elif stored_network == NetworkEnumModel.REGTEST: indexer_url = INDEXER_URL_REGTEST url = local_store.get_value(SAVED_INDEXER_URL) @@ -242,6 +248,8 @@ def get_default_proxy_endpoint() -> DefaultProxyEndpoint: proxy_endpoint = PROXY_ENDPOINT_MAINNET elif stored_network == NetworkEnumModel.TESTNET: proxy_endpoint = PROXY_ENDPOINT_TESTNET + elif stored_network == NetworkEnumModel.TESTNET4: + proxy_endpoint = PROXY_ENDPOINT_TESTNET4 elif stored_network == NetworkEnumModel.REGTEST: proxy_endpoint = PROXY_ENDPOINT_REGTEST endpoint = local_store.get_value(SAVED_PROXY_ENDPOINT) @@ -267,6 +275,8 @@ def get_default_bitcoind_host() -> DefaultBitcoindHost: bitcoind_host = BITCOIND_RPC_HOST_MAINNET elif stored_network == NetworkEnumModel.TESTNET: bitcoind_host = BITCOIND_RPC_HOST_TESTNET + elif stored_network == NetworkEnumModel.TESTNET4: + bitcoind_host = BITCOIND_RPC_HOST_TESTNET4 elif stored_network == NetworkEnumModel.REGTEST: bitcoind_host = BITCOIND_RPC_HOST_REGTEST host = local_store.get_value(SAVED_BITCOIND_RPC_HOST) @@ -292,6 +302,8 @@ def get_default_bitcoind_port() -> DefaultBitcoindPort: bitcoind_port = BITCOIND_RPC_PORT_MAINNET elif stored_network == NetworkEnumModel.TESTNET: bitcoind_port = BITCOIND_RPC_PORT_TESTNET + elif stored_network == NetworkEnumModel.TESTNET4: + bitcoind_port = BITCOIND_RPC_PORT_TESTNET4 elif stored_network == NetworkEnumModel.REGTEST: bitcoind_port = BITCOIND_RPC_PORT_REGTEST port = local_store.get_value(SAVED_BITCOIND_RPC_PORT) diff --git a/src/data/service/asset_detail_page_services.py b/src/data/service/asset_detail_page_services.py index 80cdff55..1126b608 100644 --- a/src/data/service/asset_detail_page_services.py +++ b/src/data/service/asset_detail_page_services.py @@ -176,21 +176,35 @@ def assign_transfer_status(transaction): # Assign transfer statuses based on the transaction kind if transaction.kind == AssetTransferStatusEnumModel.ISSUANCE.value: transaction.transfer_Status = TransferStatusEnumModel.INTERNAL - transaction.amount_status = f'+{ - str(transaction.amount) - }' + transaction.amount_status = f"""+{ + str(transaction.assignments[0].value) + }""" elif transaction.kind in ( AssetTransferStatusEnumModel.RECEIVE_BLIND.value, AssetTransferStatusEnumModel.RECEIVE_WITNESS.value, ): transaction.transfer_Status = TransferStatusEnumModel.RECEIVED - transaction.amount_status = f'+{ - str(transaction.amount) - }' + amount_val = None + if ( + transaction.assignments + and len(transaction.assignments) > 0 + and transaction.assignments[0].value is not None + ): + amount_val = transaction.assignments[0].value + elif ( + transaction.requested_assignment + and transaction.requested_assignment.value is not None + ): + amount_val = transaction.requested_assignment.value + + transaction.amount_status = f"+{ + str(amount_val) + if amount_val is not None else '0' + }" elif transaction.kind == AssetTransferStatusEnumModel.SEND.value: - transaction.amount_status = f'-{ - str(transaction.amount) - }' + transaction.amount_status = f"""-{ + str(transaction.requested_assignment.value) + }""" transaction.transfer_Status = TransferStatusEnumModel.SENT else: raise ServiceOperationException( diff --git a/src/data/service/backup_service.py b/src/data/service/backup_service.py index f7c6ef4d..38acbb7f 100644 --- a/src/data/service/backup_service.py +++ b/src/data/service/backup_service.py @@ -15,6 +15,7 @@ from src.utils.error_message import ERROR_UNABLE_TO_GET_PASSWORD from src.utils.gdrive_operation import GoogleDriveManager from src.utils.handle_exception import handle_exceptions +from src.utils.helpers import write_ln_node_commit_id_file from src.utils.logging import logger @@ -93,7 +94,14 @@ def backup(mnemonic: str, password: str) -> bool: success: bool = backup.upload_to_drive( file_path=backup_file_path, file_name=backup_file_name, ) - return success + + commit_id_file_path, commit_id_file_name = write_ln_node_commit_id_file( + hashed_mnemonic, + ) + commit_id_success: bool = backup.upload_to_drive( + file_path=commit_id_file_path, file_name=commit_id_file_name, + ) + return success and commit_id_success except Exception as exc: return handle_exceptions(exc) finally: diff --git a/src/data/service/helpers/faucet_service_helper.py b/src/data/service/helpers/faucet_service_helper.py index 3f74ea11..6f19fe27 100644 --- a/src/data/service/helpers/faucet_service_helper.py +++ b/src/data/service/helpers/faucet_service_helper.py @@ -6,6 +6,7 @@ from src.model.enums.enums_model import NetworkEnumModel from src.utils.constant import rgbMainnetFaucetURLs from src.utils.constant import rgbRegtestFaucetURLs +from src.utils.constant import rgbTestnet4FaucetURLs from src.utils.constant import rgbTestnetFaucetURLs from src.utils.custom_exception import ServiceOperationException from src.utils.error_message import ERROR_FAILED_TO_GET_FAUCET_URL @@ -19,6 +20,8 @@ def get_faucet_url(network: NetworkEnumModel) -> str: return rgbRegtestFaucetURLs[0] if network.value == NetworkEnumModel.TESTNET.value: return rgbTestnetFaucetURLs[0] + if network.value == NetworkEnumModel.TESTNET4.value: + return rgbTestnet4FaucetURLs[0] if network.value == NetworkEnumModel.MAINNET.value: return rgbMainnetFaucetURLs[0] raise ServiceOperationException(ERROR_INVALID_NETWORK_TYPE) diff --git a/src/data/service/helpers/main_asset_page_helper.py b/src/data/service/helpers/main_asset_page_helper.py index 82133175..ac9c92b2 100644 --- a/src/data/service/helpers/main_asset_page_helper.py +++ b/src/data/service/helpers/main_asset_page_helper.py @@ -24,7 +24,7 @@ def get_offline_asset_ticker(network: NetworkEnumModel): try: if network.value == NetworkEnumModel.REGTEST.value: return 'r' + 'BTC' - if network.value == NetworkEnumModel.TESTNET.value: + if network.value in (NetworkEnumModel.TESTNET.value, NetworkEnumModel.TESTNET4.value): return 't' + 'BTC' if network.value == NetworkEnumModel.MAINNET.value: return 'BTC' @@ -48,7 +48,7 @@ def get_asset_name(network: NetworkEnumModel): try: if network.value == NetworkEnumModel.REGTEST.value: return 'r' + 'Bitcoin' - if network.value == NetworkEnumModel.TESTNET.value: + if network.value in (NetworkEnumModel.TESTNET.value, NetworkEnumModel.TESTNET4.value): return 't' + 'Bitcoin' if network.value == NetworkEnumModel.MAINNET.value: return 'Bitcoin' diff --git a/src/data/service/restore_service.py b/src/data/service/restore_service.py index beb1d9ab..97927977 100644 --- a/src/data/service/restore_service.py +++ b/src/data/service/restore_service.py @@ -8,16 +8,19 @@ import shutil from src.data.repository.common_operations_repository import CommonOperationRepository +from src.data.repository.setting_repository import SettingRepository from src.data.service.common_operation_service import CommonOperationService from src.model.common_operation_model import RestoreRequestModel from src.model.common_operation_model import RestoreResponseModel from src.utils.build_app_path import app_paths +from src.utils.constant import COMPATIBLE_RLN_NODE_COMMITS from src.utils.custom_exception import CommonException from src.utils.error_message import ERROR_NOT_BACKUP_FILE from src.utils.error_message import ERROR_UNABLE_TO_GET_PASSWORD from src.utils.error_message import ERROR_WHILE_RESTORE_DOWNLOAD_FROM_DRIVE from src.utils.gdrive_operation import GoogleDriveManager from src.utils.handle_exception import handle_exceptions +from src.utils.helpers import read_ln_node_commit_id_file from src.utils.logging import logger @@ -66,6 +69,17 @@ def restore(mnemonic: str, password: str) -> RestoreResponseModel: # Download restore zip from Google Drive logger.info('Downloading restore zip from drive') restore = GoogleDriveManager() + commit_id_file_name = f'{hashed_mnemonic}.commit' + restore.download_from_drive( + file_name=commit_id_file_name, destination_dir=restore_folder_path, + ) + + commit_id = read_ln_node_commit_id_file(commit_id_file_name) + if commit_id not in COMPATIBLE_RLN_NODE_COMMITS: + raise CommonException('RGB_LIB_INCOMPATIBLE') + SettingRepository.set_rln_node_commit_id( + commit_id, + ) success: bool | None = restore.download_from_drive( file_name=restore_file_name, destination_dir=restore_folder_path, ) diff --git a/src/model/btc_model.py b/src/model/btc_model.py index d7ad17b3..fba60551 100644 --- a/src/model/btc_model.py +++ b/src/model/btc_model.py @@ -12,6 +12,7 @@ from src.model.enums.enums_model import TransactionStatusEnumModel from src.model.enums.enums_model import TransferStatusEnumModel +from src.model.rgb_model import AssignmentModel # -------------------- Helper models ----------------------- @@ -47,7 +48,7 @@ class Utxo(BaseModel): class RgbAllocation(BaseModel): """Model part of list unspents api response model""" asset_id: str | None = None - amount: int + assignment: AssignmentModel settled: bool @@ -71,7 +72,6 @@ class OfflineAsset(BaseModel): ticker: str balance: BalanceStatus name: str - asset_iface: str = 'BITCOIN' # -------------------- Request Models ----------------------- diff --git a/src/model/common_operation_model.py b/src/model/common_operation_model.py index 966e5c50..1fe0cb80 100644 --- a/src/model/common_operation_model.py +++ b/src/model/common_operation_model.py @@ -123,7 +123,8 @@ class NodeInfoResponseModel(BaseModel): eventual_close_fees_sat: int pending_outbound_payments_sat: int num_peers: int - onchain_pubkey: str + account_xpub_vanilla: str + account_xpub_colored: str max_media_upload_size_mb: int rgb_htlc_min_msat: int rgb_channel_capacity_min_sat: int diff --git a/src/model/enums/enums_model.py b/src/model/enums/enums_model.py index 9dcee7cc..f7338c46 100644 --- a/src/model/enums/enums_model.py +++ b/src/model/enums/enums_model.py @@ -11,6 +11,8 @@ class NetworkEnumModel(str, Enum): REGTEST = 'regtest' MAINNET = 'mainnet' TESTNET = 'testnet' + TESTNET4 = 'testnet4' + SIGNET = 'signet' class FilterAssetEnumModel(str, Enum): @@ -52,7 +54,7 @@ class AssetTransferStatusEnumModel(str, Enum): class NativeAuthType(str, Enum): """Enum for authentication type for native""" LOGGING_TO_APP = 'LOGGING_TO_APP' - # operation like issue rgb20 or rgb25 and transactions + # operation like issue nia or cfa and transactions MAJOR_OPERATION = 'MAJOR_OPERATION' @@ -71,8 +73,9 @@ class WalletType(str, Enum): class AssetType(str, Enum): """Enum for asset type""" - RGB20 = 'RGB20' - RGB25 = 'RGB25' + NIA = 'NIA' + CFA = 'CFA' + UDA = 'UDA' BITCOIN = 'BITCOIN' @@ -89,6 +92,7 @@ class TokenSymbol(str, Enum): """Enum for token symbol""" BITCOIN = 'BTC' TESTNET_BITCOIN = 'tBTC' + TESTNET4_BITCOIN = 'tBTC' REGTEST_BITCOIN = 'rBTC' SAT = 'SAT' @@ -125,3 +129,12 @@ class ChannelFetchingModel(str, Enum): FETCHING = 'fetching' FETCHED = 'fetched' FAILED = 'failed' + + +class AssignmentEnumModel(str, Enum): + """Enum for assignment""" + FUNGIBLE = 'Fungible' + NON_FUNGIBLE = 'NonFungible' + INFLATION_RIGHT = 'InflationRight' + REPLACE_RIGHT = 'ReplaceRight' + ANY = 'Any' diff --git a/src/model/rgb_model.py b/src/model/rgb_model.py index 6f9f6e8a..4e206e37 100644 --- a/src/model/rgb_model.py +++ b/src/model/rgb_model.py @@ -8,6 +8,7 @@ from pydantic import Field from pydantic import model_validator +from src.model.enums.enums_model import AssignmentEnumModel from src.model.enums.enums_model import FilterAssetEnumModel from src.model.enums.enums_model import TransferStatusEnumModel from src.model.payments_model import BaseTimeStamps @@ -26,6 +27,12 @@ class StatusModel(BaseModel): status: bool +class AssignmentModel(BaseModel): + """Assignment model""" + type: AssignmentEnumModel + value: int | None = None + + class TransactionTxModel(BaseModel): """Mode for get single transaction method of asset detail page service""" tx_id: str | None = None @@ -79,7 +86,6 @@ class Token(BaseModel): class AssetModel(BaseModel): """Model for asset """ asset_id: str - asset_iface: str ticker: str | None = None name: str details: str | None @@ -105,7 +111,8 @@ class TransferAsset(BaseTimeStamps): idx: int status: str - amount: int + requested_assignment: AssignmentModel | None + assignments: list[AssignmentModel | None] | None = [] amount_status: str | None = None # this for ui purpose kind: str transfer_Status: TransferStatusEnumModel | None = None @@ -176,13 +183,14 @@ class RgbInvoiceRequestModel(BaseModel): min_confirmations: int asset_id: str | None = None duration_seconds: int = RGB_INVOICE_DURATION_SECONDS + witness: bool = False class SendAssetRequestModel(BaseModel): """Request model for sending assets.""" asset_id: str - amount: int + assignment: AssignmentModel recipient_id: str donation: bool | None = False fee_rate: int @@ -230,9 +238,9 @@ class DecodeRgbInvoiceResponseModel(BaseModel): """Response model for decoding RGB invoices.""" recipient_id: str - asset_iface: str | None = None + asset_schema: str | None = None asset_id: str | None = None - amount: str | None = None + assignment: AssignmentModel | None network: str expiration_timestamp: int transport_endpoints: list[str] diff --git a/src/translations/en_IN.qm b/src/translations/en_IN.qm index 80711c0d..f5eda409 100644 Binary files a/src/translations/en_IN.qm and b/src/translations/en_IN.qm differ diff --git a/src/translations/en_IN.ts b/src/translations/en_IN.ts index 027b8ead..6e29a225 100644 --- a/src/translations/en_IN.ts +++ b/src/translations/en_IN.ts @@ -324,11 +324,11 @@ If you understand the above remarks and wish to proceed, press the button below CHANGE UPLOADED FILE - issue_new_rgb25_asset - Issue new RGB25 asset + issue_new_cfa_asset + Issue new CFA asset - rgb25_address_info + cfa_address_info The blinded UTXO in this invoice will expire in 24 hours after its creation and will be valid only for this asset @@ -588,8 +588,8 @@ If you understand the above remarks and wish to proceed, press the button below Fungibles - issue_new_rgb20_asset - Issue new RGB20 asset + issue_new_nia_asset + Issue new NIA asset backup_message diff --git a/src/utils/build_helpers.py b/src/utils/build_helpers.py index 5173023a..f5025f5f 100644 --- a/src/utils/build_helpers.py +++ b/src/utils/build_helpers.py @@ -20,9 +20,9 @@ def add_network_argument(parser: ArgumentParser) -> None: """Add the common --network argument to an ArgumentParser.""" parser.add_argument( '--network', - choices=['mainnet', 'testnet', 'regtest'], + choices=['mainnet', 'testnet', 'regtest', 'testnet4'], required=True, - help="Specify the network to build for: 'mainnet', 'testnet', or 'regtest'.", + help="Specify the network to build for: 'mainnet', 'testnet', 'regtest', or 'testnet4'.", ) diff --git a/src/utils/common_utils.py b/src/utils/common_utils.py index 759fe5b6..575519b4 100644 --- a/src/utils/common_utils.py +++ b/src/utils/common_utils.py @@ -152,7 +152,7 @@ def set_qr_code(data): def generate_identicon(data, size=40): - """This method generates the identicon for rgb20 asset""" + """This method generates the identicon for nia asset""" generator = pydenticon.Generator( 5, 5, foreground=[ @@ -586,6 +586,7 @@ def get_bitcoin_info_by_network(): NetworkEnumModel.MAINNET.value: ':/assets/bitcoin.png', NetworkEnumModel.REGTEST.value: ':/assets/regtest_bitcoin.png', NetworkEnumModel.TESTNET.value: ':/assets/testnet_bitcoin.png', + NetworkEnumModel.TESTNET4.value: ':/assets/testnet_bitcoin.png', } img_path = bitcoin_img_path.get(network.value) @@ -595,6 +596,8 @@ def get_bitcoin_info_by_network(): return (ticker, f'{bitcoin_asset}', img_path) if ticker == TokenSymbol.TESTNET_BITCOIN.value: return (ticker, f'{NetworkEnumModel.TESTNET.value} {bitcoin_asset}', img_path) + if ticker == TokenSymbol.TESTNET4_BITCOIN.value: + return (ticker, f'{NetworkEnumModel.TESTNET4.value} {bitcoin_asset}', img_path) if ticker == TokenSymbol.REGTEST_BITCOIN.value: return (ticker, f'{NetworkEnumModel.REGTEST.value} {bitcoin_asset}', img_path) diff --git a/src/utils/constant.py b/src/utils/constant.py index e767f867..eb2600bb 100644 --- a/src/utils/constant.py +++ b/src/utils/constant.py @@ -30,6 +30,7 @@ CACHE_FILE_NAME = { NetworkEnumModel.MAINNET: 'iris-wallet-cache-mainnet', NetworkEnumModel.TESTNET: 'iris-wallet-cache-testnet', + NetworkEnumModel.TESTNET4: 'iris-wallet-cache-testnet4', NetworkEnumModel.REGTEST: 'iris-wallet-cache-regtest', } DEFAULT_CACHE_FILENAME = 'iris-wallet-cache-default' @@ -80,6 +81,22 @@ PROXY_ENDPOINT_TESTNET = 'rpcs://proxy.iriswallet.com/0.2/json-rpc' LDK_DATA_NAME_TESTNET = 'dataldktestnet' +BITCOIND_RPC_USER_TESTNET4 = 'user' +BITCOIND_RPC_PASSWORD_TESTNET4 = 'password' +BITCOIND_RPC_HOST_TESTNET4 = 'electrum.iriswallet.com' +BITCOIND_RPC_PORT_TESTNET4 = 48332 +INDEXER_URL_TESTNET4 = 'ssl://electrum.iriswallet.com:50053' +PROXY_ENDPOINT_TESTNET4 = 'rpcs://proxy.iriswallet.com/0.2/json-rpc' +LDK_DATA_NAME_TESTNET4 = 'dataldktestnet4' + +BITCOIND_RPC_USER_SIGNET = 'user' +BITCOIND_RPC_PASSWORD_SIGNET = 'password' +BITCOIND_RPC_HOST_SIGNET = 'electrum.iriswallet.com' +BITCOIND_RPC_PORT_SIGNET = 18443 +INDEXER_URL_SIGNET = 'ssl://electrum.iriswallet.com:50033' +PROXY_ENDPOINT_SIGNET = 'rpcs://proxy.iriswallet.com/0.2/json-rpc' +LDK_DATA_NAME_SIGNET = 'dataldksignet' + BITCOIND_RPC_USER_MAINNET = 'user' BITCOIND_RPC_PASSWORD_MAINNET = 'password' BITCOIND_RPC_HOST_MAINNET = 'localhost' @@ -105,6 +122,10 @@ 'https://rgb-faucet.iriswallet.com/testnet-planb2023', 'https://rgb-faucet.iriswallet.com/testnet-random2023', ] +rgbTestnet4FaucetURLs: list[str] = [ + 'https://rgb-faucet.iriswallet.com/testnet4-planb2023', + 'https://rgb-faucet.iriswallet.com/testnet4-random2023', +] rgbMainnetFaucetURLs: list[str] = [ 'https://rgb-faucet.iriswallet.com/mainnet-random2023', ] @@ -128,9 +149,9 @@ # RGB lightning node commit ID RGB_LN_COMMIT_ID_KEY = 'rgb_ln_commit_id' -CURRENT_RLN_NODE_COMMIT = 'a623edbd7c49639dc41c72c5aef98d808d6c1d00' +CURRENT_RLN_NODE_COMMIT = '358d2d266aca22cf8e3ae142370ce5befd58ce7c' COMPATIBLE_RLN_NODE_COMMITS = [ - 'a623edbd7c49639dc41c72c5aef98d808d6c1d00', + '358d2d266aca22cf8e3ae142370ce5befd58ce7c', ] # Directory names used in paths diff --git a/src/utils/error_message.py b/src/utils/error_message.py index c2793c29..27ab398e 100644 --- a/src/utils/error_message.py +++ b/src/utils/error_message.py @@ -71,3 +71,7 @@ ERROR_UNABLE_TO_SET_MIN_CONFIRMATION = 'Unable to set min confirmation' ERROR_NODE_INCOMPATIBILITY = 'The commit ID of the RGB Lightning Node is incompatible with the one used to initialize the wallet' ERROR_WHILE_DOWNLOADING_LOGS = 'An error occurred while downloading logs.' +ERROR_INSUFFICIENT_ASSET = 'You have insufficient assets' +ERROR_INVALID_PEER_TYPE = 'The peer info is invalid' +ERROR_INVALID_PUBKEY_TYPE = 'The peer pubkey is invalid' +ERROR_NO_UNCOLORED_UTXOS_AVAILABLE = 'You do not have any available UTXOs' diff --git a/src/utils/helpers.py b/src/utils/helpers.py index 2d19425a..bcb9383d 100644 --- a/src/utils/helpers.py +++ b/src/utils/helpers.py @@ -33,26 +33,38 @@ from src.utils.constant import ANNOUNCE_ALIAS from src.utils.constant import BITCOIND_RPC_HOST_MAINNET from src.utils.constant import BITCOIND_RPC_HOST_REGTEST +from src.utils.constant import BITCOIND_RPC_HOST_SIGNET from src.utils.constant import BITCOIND_RPC_HOST_TESTNET +from src.utils.constant import BITCOIND_RPC_HOST_TESTNET4 from src.utils.constant import BITCOIND_RPC_PASSWORD_MAINNET from src.utils.constant import BITCOIND_RPC_PASSWORD_REGTEST +from src.utils.constant import BITCOIND_RPC_PASSWORD_SIGNET from src.utils.constant import BITCOIND_RPC_PASSWORD_TESTNET +from src.utils.constant import BITCOIND_RPC_PASSWORD_TESTNET4 from src.utils.constant import BITCOIND_RPC_PORT_MAINNET from src.utils.constant import BITCOIND_RPC_PORT_REGTEST +from src.utils.constant import BITCOIND_RPC_PORT_SIGNET from src.utils.constant import BITCOIND_RPC_PORT_TESTNET +from src.utils.constant import BITCOIND_RPC_PORT_TESTNET4 from src.utils.constant import BITCOIND_RPC_USER_MAINNET from src.utils.constant import BITCOIND_RPC_USER_REGTEST +from src.utils.constant import BITCOIND_RPC_USER_SIGNET from src.utils.constant import BITCOIND_RPC_USER_TESTNET +from src.utils.constant import BITCOIND_RPC_USER_TESTNET4 from src.utils.constant import DAEMON_PORT from src.utils.constant import INDEXER_URL_MAINNET from src.utils.constant import INDEXER_URL_REGTEST +from src.utils.constant import INDEXER_URL_SIGNET from src.utils.constant import INDEXER_URL_TESTNET +from src.utils.constant import INDEXER_URL_TESTNET4 from src.utils.constant import LDK_PORT from src.utils.constant import LDK_PORT_KEY from src.utils.constant import LIGHTNING_URL_KEY from src.utils.constant import PROXY_ENDPOINT_MAINNET from src.utils.constant import PROXY_ENDPOINT_REGTEST +from src.utils.constant import PROXY_ENDPOINT_SIGNET from src.utils.constant import PROXY_ENDPOINT_TESTNET +from src.utils.constant import PROXY_ENDPOINT_TESTNET4 from src.utils.constant import SAVED_ANNOUNCE_ADDRESS from src.utils.constant import SAVED_ANNOUNCE_ALIAS from src.utils.constant import SAVED_BITCOIND_RPC_HOST @@ -259,6 +271,7 @@ def get_node_arg_config(network: NetworkEnumModel) -> list: '--daemon-listening-port', str(daemon_port), '--ldk-peer-listening-port', str(ldk_port), '--network', network.value, + '--disable-authentication', ] except Exception as exc: raise exc @@ -345,6 +358,22 @@ def get_bitcoin_config(network: NetworkEnumModel, password) -> UnlockRequestMode SAVED_INDEXER_URL: INDEXER_URL_REGTEST, SAVED_PROXY_ENDPOINT: PROXY_ENDPOINT_REGTEST, }, + NetworkEnumModel.TESTNET4: { + SAVED_BITCOIND_RPC_USER: BITCOIND_RPC_USER_TESTNET4, + SAVED_BITCOIND_RPC_PASSWORD: BITCOIND_RPC_PASSWORD_TESTNET4, + SAVED_BITCOIND_RPC_HOST: BITCOIND_RPC_HOST_TESTNET4, + SAVED_BITCOIND_RPC_PORT: BITCOIND_RPC_PORT_TESTNET4, + SAVED_INDEXER_URL: INDEXER_URL_TESTNET4, + SAVED_PROXY_ENDPOINT: PROXY_ENDPOINT_TESTNET4, + }, + NetworkEnumModel.SIGNET: { + SAVED_BITCOIND_RPC_USER: BITCOIND_RPC_USER_SIGNET, + SAVED_BITCOIND_RPC_PASSWORD: BITCOIND_RPC_PASSWORD_SIGNET, + SAVED_BITCOIND_RPC_HOST: BITCOIND_RPC_HOST_SIGNET, + SAVED_BITCOIND_RPC_PORT: BITCOIND_RPC_PORT_SIGNET, + SAVED_INDEXER_URL: INDEXER_URL_SIGNET, + SAVED_PROXY_ENDPOINT: PROXY_ENDPOINT_SIGNET, + }, } # Retrieve the appropriate configuration based on the network network_config = config_mapping.get(network) or {} @@ -448,3 +477,51 @@ def check_node(context: str) -> bool: }): Error while checking if node is locked', ) return False + + +def write_ln_node_commit_id_file(file_name: str) -> tuple[str, str]: + """ + Write the ln_node commit to a .commit file in the same directory as the backup file. + + Args: + file_name (str): The name to use for the commit file (e.g., 'wallet.commit'). + + Returns: + str: The full path to the created version file. + """ + version = SettingRepository.get_rln_node_commit_id() + version_file_name = f'{file_name}.commit' + version_file_path = os.path.join( + app_paths.backup_folder_path, version_file_name, + ) + + try: + with open(version_file_path, 'w', encoding='utf-8') as f: + f.write(version) + return version_file_path, version_file_name + except OSError as e: + raise RuntimeError(f'Failed to write version file: {e}') from e + + +def read_ln_node_commit_id_file(file_name: str) -> str: + """ + Read the ln_node commit from a .commit file in the same directory as the backup file. + + Args: + file_name (str): The name of the commit file (e.g., 'wallet.commit'). + backup_path (str): The full path to the backup file (used to derive the directory). + + Returns: + str: The ln_node commit if available, otherwise "unknown". + """ + commit_id_file_path = os.path.join( + app_paths.restore_folder_path, file_name, + ) + + try: + with open(commit_id_file_path, encoding='utf-8') as f: + return f.read().strip() + except FileNotFoundError: + return 'unknown' + except OSError as e: + raise RuntimeError(f'Failed to read version file: {e}') from e diff --git a/src/utils/page_navigation.py b/src/utils/page_navigation.py index 9a0e0845..c68ca6ca 100644 --- a/src/utils/page_navigation.py +++ b/src/utils/page_navigation.py @@ -24,8 +24,8 @@ from src.views.ui_faucets import FaucetsWidget from src.views.ui_fungible_asset import FungibleAssetWidget from src.views.ui_help import HelpWidget -from src.views.ui_issue_rgb20 import IssueRGB20Widget -from src.views.ui_issue_rgb25 import IssueRGB25Widget +from src.views.ui_issue_cfa import IssueCFAWidget +from src.views.ui_issue_nia import IssueNIAWidget from src.views.ui_ln_endpoint import LnEndpointWidget from src.views.ui_network_selection_page import NetworkSelectionWidget from src.views.ui_receive_bitcoin import ReceiveBitcoinWidget @@ -62,19 +62,19 @@ def __init__(self, _ui): 'FungibleAssetWidget': FungibleAssetWidget, 'CollectiblesAssetWidget': CollectiblesAssetWidget, 'SetWalletPassword': SetWalletPasswordWidget, - 'IssueRGB20': IssueRGB20Widget, + 'IssueNIA': IssueNIAWidget, 'Bitcoin': BtcWidget, - 'IssueRGB25': IssueRGB25Widget, - 'SendRGB25': SendRGBAssetWidget, - 'ReceiveRGB25': ReceiveRGBAssetWidget, - 'RGB25Detail': RGBAssetDetailWidget, + 'IssueCFA': IssueCFAWidget, + 'SendCFA': SendRGBAssetWidget, + 'ReceiveCFA': ReceiveRGBAssetWidget, + 'CFADetail': RGBAssetDetailWidget, 'SendBitcoin': SendBitcoinWidget, 'ReceiveBitcoin': ReceiveBitcoinWidget, 'ChannelManagement': ChannelManagement, 'CreateChannel': CreateChannelWidget, 'ViewUnspentList': ViewUnspentList, 'EnterWalletPassword': EnterWalletPassword, - 'RGB25TransactionDetail': RGBAssetTransactionDetail, + 'CFATransactionDetail': RGBAssetTransactionDetail, 'BitcoinTransactionDetail': BitcoinTransactionDetail, 'Backup': Backup, 'Swap': SwapWidget, @@ -128,23 +128,23 @@ def __init__(self, _ui): self.event_based_navigation.enter_wallet_password_page_signal.connect( self.enter_wallet_password_page, ) - self.event_based_navigation.issue_rgb20_asset_page_signal.connect( - self.issue_rgb20_asset_page, + self.event_based_navigation.issue_nia_asset_page_signal.connect( + self.issue_nia_asset_page, ) self.event_based_navigation.bitcoin_page_signal.connect( self.bitcoin_page, ) - self.event_based_navigation.issue_rgb25_asset_page_signal.connect( - self.issue_rgb25_asset_page, + self.event_based_navigation.issue_cfa_asset_page_signal.connect( + self.issue_cfa_asset_page, ) - self.event_based_navigation.send_rgb25_page_signal.connect( - self.send_rgb25_page, + self.event_based_navigation.send_cfa_page_signal.connect( + self.send_cfa_page, ) - self.event_based_navigation.receive_rgb25_page_signal.connect( - self.receive_rgb25_page, + self.event_based_navigation.receive_cfa_page_signal.connect( + self.receive_cfa_page, ) - self.event_based_navigation.rgb25_detail_page_signal.connect( - self.rgb25_detail_page, + self.event_based_navigation.cfa_detail_page_signal.connect( + self.cfa_detail_page, ) self.event_based_navigation.send_bitcoin_page_signal.connect( self.send_bitcoin_page, @@ -161,8 +161,8 @@ def __init__(self, _ui): self.event_based_navigation.view_unspent_list_page_signal.connect( self.view_unspent_list_page, ) - self.event_based_navigation.rgb25_transaction_detail_page_signal.connect( - self.rgb25_transaction_detail_page, + self.event_based_navigation.cfa_transaction_detail_page_signal.connect( + self.cfa_transaction_detail_page, ) self.event_based_navigation.bitcoin_transaction_detail_page_signal.connect( self.bitcoin_transaction_detail_page, @@ -291,35 +291,35 @@ def enter_wallet_password_page(self): """This method display the set wallet password page.""" self.navigate_to_page('EnterWalletPassword') - def issue_rgb20_asset_page(self): - """This method display the issue rgb20 asset page.""" - self.navigate_to_page('IssueRGB20') + def issue_nia_asset_page(self): + """This method display the issue nia asset page.""" + self.navigate_to_page('IssueNIA') def bitcoin_page(self): """This method display the bitcoin page.""" self.navigate_to_page('Bitcoin') - def issue_rgb25_asset_page(self): - """This method display the issue rgb25 page.""" - self.navigate_to_page('IssueRGB25') + def issue_cfa_asset_page(self): + """This method display the issue cfa page.""" + self.navigate_to_page('IssueCFA') - def send_rgb25_page(self): - """This method display the send rgb25 page.""" - self.navigate_to_page('SendRGB25') + def send_cfa_page(self): + """This method display the send cfa page.""" + self.navigate_to_page('SendCFA') - def receive_rgb25_page(self, params): - """This method display the receive rgb25 asset page.""" + def receive_cfa_page(self, params): + """This method display the receive cfa asset page.""" self.current_stack = { - 'name': 'ReceiveRGB25', - 'widget': self.pages['ReceiveRGB25'](self._ui.view_model, params), + 'name': 'ReceiveCFA', + 'widget': self.pages['ReceiveCFA'](self._ui.view_model, params), } self.navigate_and_toggle(False) - def rgb25_detail_page(self, params: RgbAssetPageLoadModel): - """This method display the rgb25 detail page.""" + def cfa_detail_page(self, params: RgbAssetPageLoadModel): + """This method display the cfa detail page.""" self.current_stack = { - 'name': 'RGB25Detail', - 'widget': self.pages['RGB25Detail'](self._ui.view_model, params), + 'name': 'CFADetail', + 'widget': self.pages['CFADetail'](self._ui.view_model, params), } self.navigate_and_toggle(False) @@ -343,11 +343,11 @@ def view_unspent_list_page(self): """This method display the view unspent list page.""" self.navigate_to_page('ViewUnspentList', show_sidebar=True) - def rgb25_transaction_detail_page(self, params: TransactionDetailPageModel): - """This method display the rgb25 transaction detail page.""" + def cfa_transaction_detail_page(self, params: TransactionDetailPageModel): + """This method display the cfa transaction detail page.""" self.current_stack = { - 'name': 'RGB25TransactionDetail', - 'widget': self.pages['RGB25TransactionDetail'](self._ui.view_model, params), + 'name': 'CFATransactionDetail', + 'widget': self.pages['CFATransactionDetail'](self._ui.view_model, params), } self.navigate_and_toggle(False) diff --git a/src/utils/page_navigation_events.py b/src/utils/page_navigation_events.py index e63c292d..ce5d5616 100644 --- a/src/utils/page_navigation_events.py +++ b/src/utils/page_navigation_events.py @@ -24,18 +24,18 @@ class PageNavigationEventManager(QObject): collectibles_asset_page_signal = Signal() set_wallet_password_page_signal = Signal(object) enter_wallet_password_page_signal = Signal() - issue_rgb20_asset_page_signal = Signal() + issue_nia_asset_page_signal = Signal() bitcoin_page_signal = Signal() - issue_rgb25_asset_page_signal = Signal() - send_rgb25_page_signal = Signal() - receive_rgb25_page_signal = Signal(object) - rgb25_detail_page_signal = Signal(str) + issue_cfa_asset_page_signal = Signal() + send_cfa_page_signal = Signal() + receive_cfa_page_signal = Signal(object) + cfa_detail_page_signal = Signal(str) send_bitcoin_page_signal = Signal() receive_bitcoin_page_signal = Signal() channel_management_page_signal = Signal() create_channel_page_signal = Signal() view_unspent_list_page_signal = Signal() - rgb25_transaction_detail_page_signal = Signal(object) + cfa_transaction_detail_page_signal = Signal(object) bitcoin_transaction_detail_page_signal = Signal(object) backup_page_signal = Signal() swap_page_signal = Signal() diff --git a/src/utils/rgb_asset_helpers.py b/src/utils/rgb_asset_helpers.py new file mode 100644 index 00000000..2843c9c3 --- /dev/null +++ b/src/utils/rgb_asset_helpers.py @@ -0,0 +1,111 @@ +"""Helper utilities for RGB asset detail operations. + +This module contains validation and image handling functions extracted from +the RGBAssetDetailWidget to improve code organization and testability. +""" +from __future__ import annotations + +import re + +from PySide6.QtCore import QSize +from PySide6.QtCore import Qt +from PySide6.QtWidgets import QLabel + +from src.utils.common_utils import convert_hex_to_image +from src.utils.common_utils import resize_image + + +def is_path(file_path: str) -> bool: + """Check if the string is a valid Unix-like file path. + + Args: + file_path: String to validate as a file path. + + Returns: + True if file_path matches Unix file path pattern, False otherwise. + """ + if not isinstance(file_path, str): + return False + # Define a basic regex pattern for Unix-like file paths + pattern = r'^(\/[a-zA-Z0-9_.-]+)+\/?$' + # Check if the file_path matches the pattern + return bool(re.match(pattern, file_path)) + + +def is_hex_string(bytes_hex: str) -> bool: + """Check if the string is a valid hex string. + + Args: + bytes_hex: String to validate as hexadecimal. + + Returns: + True if bytes_hex is a valid hex string with even length, False otherwise. + """ + if len(bytes_hex) % 2 != 0: + return False + hex_pattern = re.compile(r'^[0-9a-fA-F]+$') + return bool(hex_pattern.match(bytes_hex)) + + +def set_asset_image(label_widget: QLabel, image_hex: str) -> None: + """Set the asset image on a QLabel from hex string or file path. + + Args: + label_widget: QLabel widget to set the image on. + image_hex: Hex string or file path of the image. + """ + if is_hex_string(image_hex): + pixmap = convert_hex_to_image(image_hex) + resized_image = resize_image(pixmap, 335, 335) + label_widget.setPixmap(resized_image) + else: + resized_image = resize_image(image_hex, 335, 335) + label_widget.setPixmap(resized_image) + + +def handle_img_path( + widget, image_path: str, asset_image_layout, + asset_id_frame, label_asset_name: QLabel | None = None, +) -> QLabel: + """Configure the asset detail widget based on the provided image path. + + Adjusts the layout and styles, and sets the asset image. + + Args: + widget: The parent widget to configure. + image_path: Path or hex string of the asset image. + asset_image_layout: Layout to add the image label to. + asset_id_frame: Frame containing the asset ID. + label_asset_name: Optional existing label, or None to create new one. + + Returns: + The QLabel widget containing the asset image. + """ + if not image_path: + return label_asset_name + + widget.setMinimumSize(QSize(466, 848)) + widget.setFixedWidth(499) + + if label_asset_name is None: + label_asset_name = QLabel(widget) + label_asset_name.setObjectName('label_asset_name') + + label_asset_name.setMaximumSize(QSize(335, 335)) + asset_id_frame.setMinimumSize(QSize(335, 86)) + asset_id_frame.setMaximumSize(QSize(335, 86)) + label_asset_name.setStyleSheet( + "font: 14px \"Inter\";\n" + 'color: #B3B6C3;\n' + 'background: transparent;\n' + 'border: none;\n' + 'border-radius: 8px;\n' + 'font-weight: 400;\n' + '', + ) + asset_image_layout.addWidget( + label_asset_name, 0, Qt.AlignHCenter, + ) + set_asset_image(label_asset_name, image_hex=image_path) + + return label_asset_name diff --git a/src/version.py b/src/version.py index 233addc2..caa5939e 100644 --- a/src/version.py +++ b/src/version.py @@ -6,4 +6,4 @@ """ from __future__ import annotations -__version__ = '0.2.0' +__version__ = '0.3.0' diff --git a/src/viewmodels/__init__.py b/src/viewmodels/__init__.py index 17ad7226..db07d542 100644 --- a/src/viewmodels/__init__.py +++ b/src/viewmodels/__init__.py @@ -9,7 +9,7 @@ Submodules: ----------- -- IssueRGB20ViewModel: Connects RGB20 models to IssueRGB20Widget +- IssueNIAViewModel: Connects NIA models to IssueNIAWidget to enable communication between them. - MainAssetViewModel: Connects main assets models to MainAssetWidget to enable communication between them. @@ -24,8 +24,8 @@ ------ Examples of how to use the utilities in this package: - >>> from viewmodels import IssueRGB20ViewModel - >>> model = IssueRGB20ViewModel() + >>> from viewmodels import IssueNIAViewModel + >>> model = IssueNIAViewModel() >>> print(model) """ from __future__ import annotations diff --git a/src/viewmodels/rgb_25_view_model.py b/src/viewmodels/cfa_view_model.py similarity index 85% rename from src/viewmodels/rgb_25_view_model.py rename to src/viewmodels/cfa_view_model.py index 512620b7..4f560bfb 100644 --- a/src/viewmodels/rgb_25_view_model.py +++ b/src/viewmodels/cfa_view_model.py @@ -1,6 +1,6 @@ # pylint: disable=too-many-instance-attributes # mypy: ignore-errors -"""This module contains the RGB25DetailViewModel class, which represents the view model +"""This module contains the CFADetailViewModel class, which represents the view model for the Bitcoin page activities. """ from __future__ import annotations @@ -14,8 +14,10 @@ from src.data.repository.setting_repository import SettingRepository from src.data.service.asset_detail_page_services import AssetDetailPageService from src.model.enums.enums_model import AssetType +from src.model.enums.enums_model import AssignmentEnumModel from src.model.enums.enums_model import NativeAuthType from src.model.enums.enums_model import ToastPreset +from src.model.rgb_model import AssignmentModel from src.model.rgb_model import FailTransferRequestModel from src.model.rgb_model import FailTransferResponseModel from src.model.rgb_model import ListOnAndOffChainTransfersWithBalance @@ -34,12 +36,12 @@ from src.views.components.toast import ToastManager -class RGB25ViewModel(QObject, ThreadManager): +class CFAViewModel(QObject, ThreadManager): """This class represents the activities of the bitcoin page.""" asset_info = Signal(str, str, str, str) txn_list_loaded = Signal(str, str, str, str) - send_rgb25_button_clicked = Signal(bool) + send_cfa_button_clicked = Signal(bool) message = Signal(ToastPreset, str) is_loading = Signal(bool) refresh = Signal(bool) @@ -48,7 +50,7 @@ class RGB25ViewModel(QObject, ThreadManager): def __init__(self, page_navigation: Any) -> None: super().__init__() self._page_navigation = page_navigation - self.asset_info.connect(self.get_rgb25_asset_detail) + self.asset_info.connect(self.get_cfa_asset_detail) # Initializing default values for attributes self.asset_id = None @@ -62,11 +64,11 @@ def __init__(self, page_navigation: Any) -> None: self.min_confirmation = None self.txn_list = [] - def get_rgb25_asset_detail(self, asset_id: str, asset_name: str, image_path: str, asset_type: str) -> None: - """Retrieve RGB25 asset list.""" + def get_cfa_asset_detail(self, asset_id: str, asset_name: str, image_path: str, asset_type: str) -> None: + """Retrieve CFA asset list.""" def on_success(response: ListOnAndOffChainTransfersWithBalance) -> None: - """Handle success for the RGB25 asset detail list.""" + """Handle success for the CFA asset detail list.""" self.txn_list = response self.txn_list_loaded.emit( asset_id, asset_name, image_path, asset_type, @@ -94,27 +96,27 @@ def on_error(error: CommonException) -> None: except Exception as e: on_error(CommonException(message=str(e))) - def on_success_rgb25(self, tx_id: SendAssetResponseModel) -> None: - """Handle success for sending RGB25 asset.""" + def on_success_cfa(self, tx_id: SendAssetResponseModel) -> None: + """Handle success for sending CFA asset.""" self.is_loading.emit(False) - self.send_rgb25_button_clicked.emit(False) + self.send_cfa_button_clicked.emit(False) ToastManager.success(description=INFO_ASSET_SENT.format(tx_id.txid)) - if self.asset_type == AssetType.RGB25.value: + if self.asset_type == AssetType.CFA.value: self._page_navigation.collectibles_asset_page() - elif self.asset_type == AssetType.RGB20.value: + elif self.asset_type == AssetType.NIA.value: self._page_navigation.fungibles_asset_page() def on_error(self, error: CommonException) -> None: - """Handle error for sending RGB25 asset.""" + """Handle error for sending CFA asset.""" self.is_loading.emit(False) - self.send_rgb25_button_clicked.emit(False) + self.send_cfa_button_clicked.emit(False) ToastManager.error(description=error.message) def on_success_send_rgb_asset(self, success: bool) -> None: """Callback function after native authentication is successful.""" if success: - self.send_rgb25_button_clicked.emit(True) + self.send_cfa_button_clicked.emit(True) self.is_loading.emit(True) try: self.run_in_thread( @@ -123,14 +125,16 @@ def on_success_send_rgb_asset(self, success: bool) -> None: 'args': [ SendAssetRequestModel( asset_id=self.asset_id, - amount=self.amount, + assignment=AssignmentModel( + type=AssignmentEnumModel.FUNGIBLE, value=self.amount, + ), recipient_id=self.blinded_utxo, transport_endpoints=self.transport_endpoints, fee_rate=self.fee_rate, min_confirmations=self.min_confirmation, ), ], - 'callback': self.on_success_rgb25, + 'callback': self.on_success_cfa, 'error_callback': self.on_error, }, ) @@ -147,7 +151,7 @@ def on_error_native_auth(self, error: Exception) -> None: ToastManager.error(description=description) def on_send_click(self, amount: int, blinded_utxo: str, transport_endpoints: list, fee_rate: int, min_confirmation: int) -> None: - """Starts a thread to execute the send_rgb25 function with the provided arguments.""" + """Starts a thread to execute the send_cfa function with the provided arguments.""" self.amount = amount self.blinded_utxo = blinded_utxo self.transport_endpoints = transport_endpoints @@ -167,7 +171,7 @@ def on_refresh_click(self) -> None: cache = Cache.get_cache_session() if cache is not None: cache.invalidate_cache() - self.send_rgb25_button_clicked.emit(True) + self.send_cfa_button_clicked.emit(True) self.is_loading.emit(True) def on_success_refresh() -> None: @@ -175,7 +179,7 @@ def on_success_refresh() -> None: self.is_loading.emit(False) self.refresh.emit(True) ToastManager.success(description=INFO_REFRESH_SUCCESSFULLY) - self.get_rgb25_asset_detail( + self.get_cfa_asset_detail( self.asset_id, self.asset_name, None, self.asset_type, ) @@ -206,7 +210,7 @@ def on_fail_transfer(self, batch_transfer_idx: int) -> None: def on_success_fail_transfer(response: FailTransferResponseModel) -> None: """Handle success for failing a transfer.""" if response.transfers_changed: - self.get_rgb25_asset_detail( + self.get_cfa_asset_detail( self.asset_id, self.asset_name, None, self.asset_type, ) ToastManager.success( diff --git a/src/viewmodels/channel_management_viewmodel.py b/src/viewmodels/channel_management_viewmodel.py index eded3fb6..468bd16d 100644 --- a/src/viewmodels/channel_management_viewmodel.py +++ b/src/viewmodels/channel_management_viewmodel.py @@ -24,7 +24,10 @@ from src.utils.custom_exception import CommonException from src.utils.error_message import ERROR_CREATE_UTXO from src.utils.error_message import ERROR_INSUFFICIENT_ALLOCATION_SLOT -from src.utils.error_message import ERROR_NOT_ENOUGH_UNCOLORED +from src.utils.error_message import ERROR_INSUFFICIENT_ASSET +from src.utils.error_message import ERROR_INVALID_PEER_TYPE +from src.utils.error_message import ERROR_INVALID_PUBKEY_TYPE +from src.utils.error_message import ERROR_NO_UNCOLORED_UTXOS_AVAILABLE from src.utils.error_message import ERROR_SOMETHING_WENT_WRONG from src.utils.info_message import INFO_CHANNEL_DELETED from src.utils.worker import ThreadManager @@ -124,15 +127,16 @@ def on_success(response: OpenChannelResponseModel): self.channel_created.emit() def on_error(error: CommonException): - if error.message == ERROR_INSUFFICIENT_ALLOCATION_SLOT or ERROR_NOT_ENOUGH_UNCOLORED: + if error.message in (ERROR_INVALID_PUBKEY_TYPE, ERROR_INVALID_PEER_TYPE, ERROR_INSUFFICIENT_ASSET): + ToastManager.error(description=error.message) + self.is_loading.emit(False) + elif error.message in (ERROR_INSUFFICIENT_ALLOCATION_SLOT, ERROR_NO_UNCOLORED_UTXOS_AVAILABLE): params = HandleInsufficientAllocationSlotsModel( capacity_sat=capacity_sat, pub_key=pub_key, push_msat=push_msat, asset_id=asset_id, amount=amount, ) self.handle_insufficient_allocation(params) else: - ToastManager.error( - description=error.message, - ) + ToastManager.error(description=error.message) self.is_loading.emit(False) self.run_in_thread( diff --git a/src/viewmodels/issue_rgb25_view_model.py b/src/viewmodels/issue_cfa_view_model.py similarity index 86% rename from src/viewmodels/issue_rgb25_view_model.py rename to src/viewmodels/issue_cfa_view_model.py index c11fe6f2..0873d8ed 100644 --- a/src/viewmodels/issue_rgb25_view_model.py +++ b/src/viewmodels/issue_cfa_view_model.py @@ -1,6 +1,6 @@ """ -This module contains the IssueRGB25ViewModel class, which represents the view model -for the Issue RGB25 Asset page activities. +This module contains the IssueCFAViewModel class, which represents the view model +for the Issue CFA Asset page activities. """ from __future__ import annotations @@ -26,12 +26,12 @@ from src.views.components.toast import ToastManager -class IssueRGB25ViewModel(QObject, ThreadManager): - """This class represents the activities of the Issue RGB25 Asset page.""" +class IssueCFAViewModel(QObject, ThreadManager): + """This class represents the activities of the Issue CFA Asset page.""" is_loading = Signal(bool) file_upload_message = Signal(str) success_page_message = Signal(str) - rgb25_success_message = Signal(str) + cfa_success_message = Signal(str) def __init__(self, page_navigation) -> None: """ @@ -47,7 +47,7 @@ def __init__(self, page_navigation) -> None: self.amount = None self.asset_name = None - def on_success_native_auth_rgb25(self, success: bool): + def on_success_native_auth_cfa(self, success: bool): """Callback function after native authentication successful""" try: if self.amount is None or self.asset_name is None or self.asset_ticker is None: @@ -87,7 +87,7 @@ def on_success_native_auth_rgb25(self, success: bool): description=ERROR_SOMETHING_WENT_WRONG, ) - def on_error_native_auth_rgb25(self, error: Exception): + def on_error_native_auth_cfa(self, error: Exception): """Callback function on error""" self.is_loading.emit(False) err_message = error.message if isinstance( @@ -115,7 +115,7 @@ def open_file_dialog(self) -> None: ) def on_success(self, response: IssueAssetResponseModel): - """on success callback of issue rgb25 """ + """on success callback of issue cfa """ ToastManager.success( description=INFO_ASSET_ISSUED.format(response.asset_id), ) @@ -123,18 +123,18 @@ def on_success(self, response: IssueAssetResponseModel): self.is_loading.emit(False) def on_error(self, error: CommonException): - """on error callback of issue rgb25 """ + """on error callback of issue cfa """ ToastManager.error( description=error.message, ) self.is_loading.emit(False) - def issue_rgb25_asset( + def issue_cfa_asset( self, asset_ticker, asset_name, amount, ): - """Issue an RGB25 asset with the provided details.""" + """Issue an CFA asset with the provided details.""" self.is_loading.emit(True) self.asset_name = asset_name self.asset_ticker = asset_ticker @@ -143,7 +143,7 @@ def issue_rgb25_asset( SettingRepository.native_authentication, { 'args': [NativeAuthType.MAJOR_OPERATION], - 'callback': self.on_success_native_auth_rgb25, - 'error_callback': self.on_error_native_auth_rgb25, + 'callback': self.on_success_native_auth_cfa, + 'error_callback': self.on_error_native_auth_cfa, }, ) diff --git a/src/viewmodels/issue_rgb20_view_model.py b/src/viewmodels/issue_nia_view_model.py similarity index 81% rename from src/viewmodels/issue_rgb20_view_model.py rename to src/viewmodels/issue_nia_view_model.py index e8496921..d91035da 100644 --- a/src/viewmodels/issue_rgb20_view_model.py +++ b/src/viewmodels/issue_nia_view_model.py @@ -1,4 +1,4 @@ -"""This module contains the IssueRGB20ViewModel class, which represents the view model +"""This module contains the IssueNIAViewModel class, which represents the view model for the issue RG20 page activities. """ from __future__ import annotations @@ -19,8 +19,8 @@ from src.views.components.toast import ToastManager -class IssueRGB20ViewModel(QObject, ThreadManager): - """This class represents the activities of the issue RGB20 page.""" +class IssueNIAViewModel(QObject, ThreadManager): + """This class represents the activities of the issue NIA page.""" issue_button_clicked = Signal(bool) close_button_clicked = Signal(bool) @@ -33,7 +33,7 @@ def __init__(self, page_navigation: Any) -> None: super().__init__() self._page_navigation = page_navigation - def on_success_native_auth_rgb20(self, success: bool): + def on_success_native_auth_nia(self, success: bool): """Callback function after native authentication successful""" try: if not success: @@ -64,7 +64,7 @@ def on_success_native_auth_rgb20(self, success: bool): description=ERROR_SOMETHING_WENT_WRONG, ) - def on_error_native_auth_rgb20(self, error: Exception): + def on_error_native_auth_nia(self, error: Exception): """Callback function on error""" self.issue_button_clicked.emit(False) description = error.message if isinstance( @@ -75,7 +75,7 @@ def on_error_native_auth_rgb20(self, error: Exception): def on_issue_click(self, short_identifier: str, asset_name: str, amount: str): """" Executes the set_wallet_password method in a separate thread. - This method starts a thread to execute the issue_rgb20 function with the provided arguments. + This method starts a thread to execute the issue_nia function with the provided arguments. It emits a signal to indicate loading state and defines a callback for when the operation is successful. """ self.issue_button_clicked.emit(True) @@ -86,23 +86,23 @@ def on_issue_click(self, short_identifier: str, asset_name: str, amount: str): SettingRepository.native_authentication, { 'args': [NativeAuthType.MAJOR_OPERATION], - 'callback': self.on_success_native_auth_rgb20, - 'error_callback': self.on_error_native_auth_rgb20, + 'callback': self.on_success_native_auth_nia, + 'error_callback': self.on_error_native_auth_nia, }, ) def on_success(self, response: IssueAssetResponseModel) -> None: - """This method is used handle onsuccess for the RGB20 issue page.""" + """This method is used handle onsuccess for the NIA issue page.""" self.issue_button_clicked.emit(False) self.is_issued.emit(response.name) def on_error(self, error) -> None: - """This method is used handle onerror for the RGB20 issue page.""" + """This method is used handle onerror for the NIA issue page.""" self.issue_button_clicked.emit(False) ToastManager.error( description=error.message, ) def on_close_click(self) -> None: - """This method is used for close the RGB20 issue page.""" + """This method is used for close the NIA issue page.""" self._page_navigation.fungibles_asset_page() diff --git a/src/viewmodels/main_view_model.py b/src/viewmodels/main_view_model.py index d0f710af..8022d78d 100644 --- a/src/viewmodels/main_view_model.py +++ b/src/viewmodels/main_view_model.py @@ -8,20 +8,20 @@ from src.viewmodels.backup_view_model import BackupViewModel from src.viewmodels.bitcoin_view_model import BitcoinViewModel +from src.viewmodels.cfa_view_model import CFAViewModel from src.viewmodels.channel_management_viewmodel import ChannelManagementViewModel from src.viewmodels.enter_password_view_model import EnterWalletPasswordViewModel from src.viewmodels.faucets_view_model import FaucetsViewModel from src.viewmodels.fee_rate_view_model import EstimateFeeViewModel from src.viewmodels.header_frame_view_model import HeaderFrameViewModel -from src.viewmodels.issue_rgb20_view_model import IssueRGB20ViewModel -from src.viewmodels.issue_rgb25_view_model import IssueRGB25ViewModel +from src.viewmodels.issue_cfa_view_model import IssueCFAViewModel +from src.viewmodels.issue_nia_view_model import IssueNIAViewModel from src.viewmodels.ln_endpoint_view_model import LnEndpointViewModel from src.viewmodels.ln_offchain_view_model import LnOffChainViewModel from src.viewmodels.main_asset_view_model import MainAssetViewModel from src.viewmodels.receive_bitcoin_view_model import ReceiveBitcoinViewModel -from src.viewmodels.receive_rgb25_view_model import ReceiveRGB25ViewModel +from src.viewmodels.receive_cfa_view_model import ReceiveCFAViewModel from src.viewmodels.restore_view_model import RestoreViewModel -from src.viewmodels.rgb_25_view_model import RGB25ViewModel from src.viewmodels.send_bitcoin_view_model import SendBitcoinViewModel from src.viewmodels.set_wallet_password_view_model import SetWalletPasswordViewModel from src.viewmodels.setting_view_model import SettingViewModel @@ -45,7 +45,7 @@ def __init__(self, page_navigation): self.main_asset_view_model = MainAssetViewModel(self.page_navigation) - self.issue_rgb20_asset_view_model = IssueRGB20ViewModel( + self.issue_nia_asset_view_model = IssueNIAViewModel( self.page_navigation, ) self.set_wallet_password_view_model = SetWalletPasswordViewModel( @@ -65,20 +65,20 @@ def __init__(self, page_navigation): self.unspent_view_model = UnspentListViewModel(self.page_navigation) - self.issue_rgb25_asset_view_model = IssueRGB25ViewModel( + self.issue_cfa_asset_view_model = IssueCFAViewModel( self.page_navigation, ) self.ln_endpoint_view_model = LnEndpointViewModel( self.page_navigation, ) - self.rgb25_view_model = RGB25ViewModel( + self.cfa_view_model = CFAViewModel( self.page_navigation, ) self.enter_wallet_password_view_model = EnterWalletPasswordViewModel( self.page_navigation, ) - self.receive_rgb25_view_model = ReceiveRGB25ViewModel( + self.receive_cfa_view_model = ReceiveCFAViewModel( self.page_navigation, ) diff --git a/src/viewmodels/receive_rgb25_view_model.py b/src/viewmodels/receive_cfa_view_model.py similarity index 88% rename from src/viewmodels/receive_rgb25_view_model.py rename to src/viewmodels/receive_cfa_view_model.py index 09469d26..98a8f91e 100644 --- a/src/viewmodels/receive_rgb25_view_model.py +++ b/src/viewmodels/receive_cfa_view_model.py @@ -1,6 +1,6 @@ """ -This module contains the ReceiveRGB25ViewModel class, which represents the view model -for the Receive RGB25 Asset page activities. +This module contains the ReceiveCFAViewModel class, which represents the view model +for the Receive CFA Asset page activities. """ from __future__ import annotations @@ -16,8 +16,8 @@ from src.views.components.toast import ToastManager -class ReceiveRGB25ViewModel(QObject, ThreadManager): - """This class represents the activities of the Receive RGB25 Asset page.""" +class ReceiveCFAViewModel(QObject, ThreadManager): + """This class represents the activities of the Receive CFA Asset page.""" address = Signal(str) message = Signal(ToastPreset, str) show_loading = Signal(bool) diff --git a/src/viewmodels/restore_view_model.py b/src/viewmodels/restore_view_model.py index d95d70be..09c3bccd 100644 --- a/src/viewmodels/restore_view_model.py +++ b/src/viewmodels/restore_view_model.py @@ -19,9 +19,14 @@ from src.utils.error_message import ERROR_WHILE_RESTORE from src.utils.gauth import authenticate from src.utils.info_message import INFO_RESTORE_COMPLETED +from src.utils.info_message import INFO_WALLET_RESET from src.utils.keyring_storage import set_value +from src.utils.local_store import local_store +from src.utils.logging import logger +from src.utils.reset_app import delete_app_data from src.utils.worker import ThreadManager from src.views.components.keyring_error_dialog import KeyringErrorDialog +from src.views.components.node_incompatibility import NodeIncompatibilityDialog class RestoreViewModel(QObject, ThreadManager): @@ -86,7 +91,9 @@ def on_error(self, exc: Exception): exc (Exception): The exception that was raised. """ self.is_loading.emit(False) - if isinstance(exc, CommonException): + if isinstance(exc, CommonException) and getattr(exc, 'message', '') == 'RGB_LIB_INCOMPATIBLE': + self.handle_rgb_lib_incompatibility() + elif isinstance(exc, CommonException): self.message.emit( ToastPreset.ERROR, exc.message, @@ -124,3 +131,28 @@ def restore(self, mnemonic: str, password: str): except Exception as exc: self.is_loading.emit(False) self.on_error(exc) + + def handle_rgb_lib_incompatibility(self): + """Handles the case when the RGB lib version is incompatible.""" + rgb_lib_incompatible_dialog = NodeIncompatibilityDialog() + rgb_lib_incompatible_dialog.show_node_incompatibility_dialog() + clicked_button = rgb_lib_incompatible_dialog.node_incompatibility_dialog.clickedButton() + + if clicked_button == rgb_lib_incompatible_dialog.close_button: + QApplication.instance().exit() + + elif clicked_button == rgb_lib_incompatible_dialog.delete_app_data_button: + rgb_lib_incompatible_dialog.show_confirmation_dialog() + confirm_button = rgb_lib_incompatible_dialog.confirmation_dialog.clickedButton() + + if confirm_button == rgb_lib_incompatible_dialog.confirm_delete_button: + self.on_delete_app_data() + elif confirm_button == rgb_lib_incompatible_dialog.cancel: + self.handle_rgb_lib_incompatibility() + + def on_delete_app_data(self): + """This function deletes the wallet data after user confirms when using an invalid rgb lib""" + basepath = local_store.get_path() + network_type = SettingRepository.get_wallet_network() + delete_app_data(basepath, network=network_type.value) + logger.info(INFO_WALLET_RESET) diff --git a/src/views/__init__.py b/src/views/__init__.py index 811269c6..d99c4d0e 100644 --- a/src/views/__init__.py +++ b/src/views/__init__.py @@ -14,7 +14,7 @@ - Sidebar: Contains UI elements and functions for the sidebar navigation. - SetWalletPasswordWidget: Contains UI elements and functions for setting the wallet password. - MainAssetWidget: Contains UI elements and functions for displaying and managing main assets. -- IssueRGB20Widget: Contains UI elements and functions for issuing RGB20 tokens. +- IssueNIAWidget: Contains UI elements and functions for issuing NIA tokens. - MainWindow: Contains the main application window and manages the primary layout and interactions. Usage: diff --git a/src/views/components/settings_helpers.py b/src/views/components/settings_helpers.py new file mode 100644 index 00000000..f2f7a06f --- /dev/null +++ b/src/views/components/settings_helpers.py @@ -0,0 +1,173 @@ +"""Helper utilities for settings widget configuration. + +This module contains helper functions for settings frame operations, +network endpoint configuration, and keyring state management. +""" +from __future__ import annotations + +from PySide6.QtCore import QCoreApplication +from PySide6.QtCore import Qt +from PySide6.QtWidgets import QDialog + +from src.data.repository.setting_repository import SettingRepository +from src.model.enums.enums_model import NetworkEnumModel +from src.utils.constant import BITCOIND_RPC_HOST_MAINNET +from src.utils.constant import BITCOIND_RPC_HOST_REGTEST +from src.utils.constant import BITCOIND_RPC_HOST_TESTNET +from src.utils.constant import BITCOIND_RPC_HOST_TESTNET4 +from src.utils.constant import BITCOIND_RPC_PORT_MAINNET +from src.utils.constant import BITCOIND_RPC_PORT_REGTEST +from src.utils.constant import BITCOIND_RPC_PORT_TESTNET +from src.utils.constant import BITCOIND_RPC_PORT_TESTNET4 +from src.utils.constant import INDEXER_URL_MAINNET +from src.utils.constant import INDEXER_URL_REGTEST +from src.utils.constant import INDEXER_URL_TESTNET +from src.utils.constant import INDEXER_URL_TESTNET4 +from src.utils.constant import IRIS_WALLET_TRANSLATIONS_CONTEXT +from src.utils.constant import PROXY_ENDPOINT_MAINNET +from src.utils.constant import PROXY_ENDPOINT_REGTEST +from src.utils.constant import PROXY_ENDPOINT_TESTNET +from src.utils.constant import PROXY_ENDPOINT_TESTNET4 +from src.utils.constant import WALLET_PASSWORD_KEY +from src.utils.keyring_storage import get_value +from src.views.ui_restore_mnemonic import RestoreMnemonicWidget + + +def set_endpoint_based_on_network() -> tuple[str, str, str, int]: + """Set various endpoints based on the currently selected wallet network. + + Returns: + Tuple containing (indexer_url, proxy_endpoint, bitcoind_host, bitcoind_port) + + Raises: + ValueError: If the stored network type is unsupported. + """ + network_config_map = { + NetworkEnumModel.MAINNET: ( + INDEXER_URL_MAINNET, PROXY_ENDPOINT_MAINNET, + BITCOIND_RPC_HOST_MAINNET, BITCOIND_RPC_PORT_MAINNET, + ), + NetworkEnumModel.TESTNET: ( + INDEXER_URL_TESTNET, PROXY_ENDPOINT_TESTNET, + BITCOIND_RPC_HOST_TESTNET, BITCOIND_RPC_PORT_TESTNET, + ), + NetworkEnumModel.TESTNET4: ( + INDEXER_URL_TESTNET4, PROXY_ENDPOINT_TESTNET4, + BITCOIND_RPC_HOST_TESTNET4, BITCOIND_RPC_PORT_TESTNET4, + ), + NetworkEnumModel.REGTEST: ( + INDEXER_URL_REGTEST, PROXY_ENDPOINT_REGTEST, + BITCOIND_RPC_HOST_REGTEST, BITCOIND_RPC_PORT_REGTEST, + ), + } + stored_network: NetworkEnumModel = SettingRepository.get_wallet_network() + config = network_config_map.get(stored_network) + if config: + return config + raise ValueError(f"Unsupported network type: {stored_network}") + + +def set_frame_content( + frame, input_value, expiry_time_unit=None, + validator=None, time_unit_combobox=None, suggestion_desc=None, +): + """Set the content for a given settings frame. + + Args: + frame: The frame to configure. + input_value: The value to set in the input field. + expiry_time_unit: Current expiry time unit for comparison. + validator: Optional validator for the input field. + time_unit_combobox: Optional time unit combobox widget. + suggestion_desc: Optional suggestion description widget. + """ + if isinstance(input_value, float) and input_value.is_integer(): + input_value = int(input_value) + + frame.input_value.setText(str(input_value)) + frame.input_value.setPlaceholderText(str(input_value)) + frame.input_value.setValidator(validator) + + if not suggestion_desc: + frame.suggestion_desc.hide() + + if time_unit_combobox: + index = time_unit_combobox.findText( + expiry_time_unit, Qt.MatchFixedString, + ) + if index != -1: + time_unit_combobox.setCurrentIndex(index) + else: + frame.time_unit_combobox.hide() + + frame.input_value.textChanged.connect( + lambda: update_save_button(frame, input_value, expiry_time_unit), + ) + + if time_unit_combobox: + frame.time_unit_combobox.currentTextChanged.connect( + lambda: update_save_button( + frame, input_value, expiry_time_unit, time_unit_combobox, + ), + ) + + # Initial call to set the correct button state + update_save_button( + frame, input_value, + expiry_time_unit, time_unit_combobox, + ) + + +def update_save_button(frame, input_value, expiry_time_unit: str, time_unit_combobox=None): + """Update the state of the save button based on input changes. + + Args: + frame: The frame containing the save button. + input_value: The original value to compare against. + expiry_time_unit: Current expiry time unit for comparison. + time_unit_combobox: Optional time unit combobox widget. + """ + current_text = frame.input_value.text().strip() + current_unit = frame.time_unit_combobox.currentText() if time_unit_combobox else '' + + time_unit_changed = current_unit != expiry_time_unit + + if current_text and (current_text != str(input_value) or (time_unit_combobox and time_unit_changed)): + frame.save_button.setDisabled(False) + else: + frame.save_button.setDisabled(True) + + +def check_keyring_state_for_password(parent, view_model) -> str | None: + """Check keyring status and retrieve wallet password. + + Retrieves password either from secure storage if keyring is disabled, + or via user prompt through a mnemonic dialog if enabled. + + Args: + parent: Parent widget for dialog display. + view_model: The main view model. + + Returns: + The wallet password string if retrieved, None otherwise. + """ + keyring_status = SettingRepository.get_keyring_status() + if keyring_status is False: + network: NetworkEnumModel = SettingRepository.get_wallet_network() + password: str = get_value(WALLET_PASSWORD_KEY, network.value) + return password + if keyring_status is True: + mnemonic_dialog = RestoreMnemonicWidget( + parent=parent, view_model=view_model, origin_page='setting_card', mnemonic_visibility=False, + ) + mnemonic_dialog.mnemonic_detail_text_label.setText( + QCoreApplication.translate( + IRIS_WALLET_TRANSLATIONS_CONTEXT, 'lock_unlock_password_required', None, + ), + ) + mnemonic_dialog.mnemonic_detail_text_label.setFixedHeight(40) + result = mnemonic_dialog.exec() + if result == QDialog.Accepted: + password = mnemonic_dialog.password_input.text() + return password + return None diff --git a/src/views/main_window.py b/src/views/main_window.py index 1c0a7075..069d77ed 100644 --- a/src/views/main_window.py +++ b/src/views/main_window.py @@ -124,6 +124,8 @@ def set_app_icon(self): app_icon = QIcon(':/assets/icons/regtest-icon.ico') if network.value == NetworkEnumModel.TESTNET.value: app_icon = QIcon(':/assets/icons/testnet-icon.ico') + if network.value == NetworkEnumModel.TESTNET4.value: + app_icon = QIcon(':/assets/icons/testnet-icon.ico') if network.value == NetworkEnumModel.MAINNET.value: app_icon = QIcon(':/assets/icons/mainnet-icon.ico') self.main_window.setWindowIcon(app_icon) diff --git a/src/views/qss/issue_rgb25_style.qss b/src/views/qss/issue_cfa_style.qss similarity index 78% rename from src/views/qss/issue_rgb25_style.qss rename to src/views/qss/issue_cfa_style.qss index c7db3596..1bb47ede 100644 --- a/src/views/qss/issue_rgb25_style.qss +++ b/src/views/qss/issue_cfa_style.qss @@ -1,5 +1,5 @@ -/* Styles for the issue RGB25 card */ -QWidget#issue_rgb_25_card { +/* Styles for the issue CFA card */ +QWidget#issue_cfa_card { background: transparent; background-color: rgb(3, 11, 37); border: 1px solid rgb(102, 108, 129); @@ -10,7 +10,7 @@ QWidget#issue_rgb_25_card { max-height: 608px; } -/* Styles for line edit elements within the issue RGB25 card */ +/* Styles for line edit elements within the issue CFA card */ QLineEdit#asset_description_input, QLineEdit#name_of_the_asset_input, QLineEdit#amount_input { padding-left: 10px; font: 15px "Inter"; @@ -21,14 +21,14 @@ QLineEdit#asset_description_input, QLineEdit#name_of_the_asset_input, QLineEdit# border-radius: 4px; } -/* Styles for line elements within the issue RGB25 card */ +/* Styles for line elements within the issue CFA card */ QFrame#line_top, QFrame#line_bottom, QFrame#line_6 { border: none; border-bottom: 1px solid rgb(27, 35, 59); } -/* Styles for label elements within the issue RGB25 card */ -QLabel#asset_name_label, QLabel#asset_description_label, QLabel#total_supply_label, QLabel#asset_file, QLabel#issue_rgb_25_asset_title_label { +/* Styles for label elements within the issue CFA card */ +QLabel#asset_name_label, QLabel#asset_description_label, QLabel#total_supply_label, QLabel#asset_file, QLabel#issue_cfa_asset_title_label { font: 14px "Inter"; color: rgb(121, 128, 148); background: transparent; @@ -36,7 +36,7 @@ QLabel#asset_name_label, QLabel#asset_description_label, QLabel#total_supply_lab font-weight: 600; } -QLabel#issue_rgb_25_asset_title_label { +QLabel#issue_cfa_asset_title_label { font: 24px "Inter"; color: #D0D3DD; padding-bottom: 15px; @@ -72,8 +72,8 @@ QPushButton#upload_file:hover { border-radius: 4px; } -/* Styles for the RGB25 close button */ -QPushButton#rgb_25_close_btn { +/* Styles for the CFA close button */ +QPushButton#cfa_close_btn { background: transparent; border: none; min-width: 24px; diff --git a/src/views/qss/issue_rgb20_style.qss b/src/views/qss/issue_nia_style.qss similarity index 63% rename from src/views/qss/issue_rgb20_style.qss rename to src/views/qss/issue_nia_style.qss index 2d6da3ba..95b4bb83 100644 --- a/src/views/qss/issue_rgb20_style.qss +++ b/src/views/qss/issue_nia_style.qss @@ -11,8 +11,8 @@ QLabel#logo_title { color: white; } -/* Styles for the issue RGB20 widget */ -QWidget#issue_rgb20_widget { +/* Styles for the issue NIA widget */ +QWidget#issue_nia_widget { background: transparent; background-color: rgb(3, 11, 37); border: 1px solid rgb(102, 108, 129); @@ -20,8 +20,8 @@ QWidget#issue_rgb20_widget { } -/* Styles for line edit elements within the issue RGB20 widget */ -QLineEdit#issue_rgb20_input, QLineEdit#asset_name_input, QLineEdit#amount_input { +/* Styles for line edit elements within the issue NIA widget */ +QLineEdit#issue_nia_input, QLineEdit#asset_name_input, QLineEdit#amount_input { padding-left: 10px; font: 15px "Inter"; color: rgb(102, 108, 129); @@ -30,8 +30,8 @@ QLineEdit#issue_rgb20_input, QLineEdit#asset_name_input, QLineEdit#amount_input border-radius: 4px; } -/* Styles for label elements within the issue RGB20 widget */ -QLabel#issue_rgb20_label, QLabel#asset_ticker_label, QLabel#asset_name_label, QLabel#total_supply_label { +/* Styles for label elements within the issue NIA widget */ +QLabel#issue_nia_label, QLabel#asset_ticker_label, QLabel#asset_name_label, QLabel#total_supply_label { font: 14px "Inter"; color: rgb(121, 128, 148); background: transparent; @@ -39,7 +39,7 @@ QLabel#issue_rgb20_label, QLabel#asset_ticker_label, QLabel#asset_name_label, QL font-weight: 600; } -/* Styles for the issue RGB20 title */ +/* Styles for the issue NIA title */ QLabel#set_wallet_password_label { font: 24px "Inter"; border: none; @@ -50,17 +50,17 @@ QLabel#set_wallet_password_label { } -/* Styles for the RGB20 close button */ -QPushButton#rgb_20_close_btn { +/* Styles for the NIA close button */ +QPushButton#nia_close_btn { background: transparent; border: none; } -QFrame#issue_rgb20_wallet_logo{ +QFrame#issue_nia_wallet_logo{ border:none } -/* Styles for line elements within the issue RGB20 widget */ +/* Styles for line elements within the issue NIA widget */ QFrame#line_3, QFrame#bottom_line_frame { border: none; border-bottom: 1px solid rgb(27, 35, 59); diff --git a/src/views/ui_backup.py b/src/views/ui_backup.py index d80c910e..b97f2377 100644 --- a/src/views/ui_backup.py +++ b/src/views/ui_backup.py @@ -468,8 +468,8 @@ def close_button_navigation(self): navigation_map = { 'fungibles': self._view_model.page_navigation.fungibles_asset_page, - 'RGB20': self._view_model.page_navigation.fungibles_asset_page, - 'RGB25': self._view_model.page_navigation.collectibles_asset_page, + 'NIA': self._view_model.page_navigation.fungibles_asset_page, + 'CFA': self._view_model.page_navigation.collectibles_asset_page, 'create_invoice': self._view_model.page_navigation.fungibles_asset_page, 'channel_management': self._view_model.page_navigation.channel_management_page, 'collectibles': self._view_model.page_navigation.collectibles_asset_page, @@ -487,9 +487,9 @@ def close_button_navigation(self): ToastManager.show_toast( parent=self, preset=ToastPreset.ERROR, - description=f'No navigation defined for { + description=f"""No navigation defined for { originating_page - }', + }""", ) def handle_mnemonic_visibility(self): diff --git a/src/views/ui_collectible_asset.py b/src/views/ui_collectible_asset.py index a7968cec..fb2d119c 100644 --- a/src/views/ui_collectible_asset.py +++ b/src/views/ui_collectible_asset.py @@ -19,7 +19,8 @@ from PySide6.QtWidgets import QWidget import src.resources_rc -from accessible_constant import ISSUE_RGB25_ASSET +from accessible_constant import ISSUE_CFA_ASSET +from src.model.enums.enums_model import AssetType from src.model.enums.enums_model import ToastPreset from src.model.rgb_model import RgbAssetPageLoadModel from src.utils.clickable_frame import ClickableFrame @@ -81,7 +82,7 @@ def __init__(self, view_model): title_name='collectibles', title_logo_path=':/assets/my_asset.png', ) self.collectible_header_frame.action_button.setAccessibleName( - ISSUE_RGB25_ASSET, + ISSUE_CFA_ASSET, ) self.vertical_layout_2.addWidget(self.collectible_header_frame) @@ -212,7 +213,7 @@ def create_collectible_frame(self, coll_asset): coll_asset.asset_id, coll_asset.name, image_path=image_path, - asset_type=coll_asset.asset_iface, + asset_type=AssetType.CFA, ) collectibles_frame.setObjectName('collectibles_frame') collectibles_frame.setCursor( @@ -304,7 +305,7 @@ def setup_ui_connection(self): ) self.collectible_header_frame.action_button.clicked.connect( lambda: self._view_model.main_asset_view_model.navigate_issue_asset( - self._view_model.page_navigation.issue_rgb25_asset_page, + self._view_model.page_navigation.issue_cfa_asset_page, ), ) self._view_model.main_asset_view_model.loading_started.connect( @@ -342,10 +343,10 @@ def handle_collectible_frame_click(self, asset_id, asset_name, image_path, asset """This method handles collectibles asset click of the main asset page.""" if asset_id is None or asset_name is None or image_path is None or asset_type is None: return - self._view_model.rgb25_view_model.asset_info.emit( + self._view_model.cfa_view_model.asset_info.emit( asset_id, asset_name, image_path, asset_type, ) - self._view_model.page_navigation.rgb25_detail_page( + self._view_model.page_navigation.cfa_detail_page( RgbAssetPageLoadModel(asset_type=asset_type), ) diff --git a/src/views/ui_create_ln_invoice.py b/src/views/ui_create_ln_invoice.py index 09a7509e..10acee00 100644 --- a/src/views/ui_create_ln_invoice.py +++ b/src/views/ui_create_ln_invoice.py @@ -437,7 +437,7 @@ def retranslate_ui(self): def on_close(self): """Navigate to the fungibles page.""" - if self.asset_type == AssetType.RGB25.value: + if self.asset_type == AssetType.CFA.value: self._view_model.page_navigation.collectibles_asset_page() else: self._view_model.page_navigation.fungibles_asset_page() @@ -500,7 +500,7 @@ def get_ln_invoice(self): asset_id=self.asset_id, amount=self.amount_input.text(), expiry=expiry_time, amount_msat=push_msat, ) self.render_timer.stop() - self._view_model.page_navigation.receive_rgb25_page( + self._view_model.page_navigation.receive_cfa_page( params=AssetDataModel( asset_type='create_invoice', close_page_navigation=self.asset_type, diff --git a/src/views/ui_fungible_asset.py b/src/views/ui_fungible_asset.py index 47bc8f28..921531c4 100644 --- a/src/views/ui_fungible_asset.py +++ b/src/views/ui_fungible_asset.py @@ -24,7 +24,7 @@ import src.resources_rc from accessible_constant import FUNGIBLES_SCROLL_WIDGETS -from accessible_constant import ISSUE_RGB20_ASSET +from accessible_constant import ISSUE_NIA_ASSET from src.data.repository.setting_repository import SettingRepository from src.data.service.common_operation_service import CommonOperationService from src.model.enums.enums_model import AssetType @@ -84,7 +84,7 @@ def __init__(self, view_model): self.title_frame = HeaderFrame( title_logo_path=':/assets/my_asset.png', title_name='fungibles', ) - self.title_frame.action_button.setAccessibleName(ISSUE_RGB20_ASSET) + self.title_frame.action_button.setAccessibleName(ISSUE_NIA_ASSET) self.fungible_frame = None self.vertical_layout_fungible_frame = None self.grid_layout_fungible_frame = None @@ -231,6 +231,7 @@ def show_assets(self): bitcoin_img_path = { NetworkEnumModel.MAINNET.value: ':/assets/bitcoin.png', NetworkEnumModel.REGTEST.value: ':/assets/regtest_bitcoin.png', + NetworkEnumModel.TESTNET4.value: ':/assets/testnet_bitcoin.png', NetworkEnumModel.TESTNET.value: ':/assets/testnet_bitcoin.png', } @@ -273,10 +274,64 @@ def show_assets(self): ), ) + def _get_asset_logo_pixmap(self, asset, img_path): + """Generate and return the pixmap for the asset logo.""" + if img_path: + return QPixmap(img_path) + + img_str = generate_identicon(asset.asset_id) + image = QImage.fromData(QByteArray.fromBase64(img_str.encode())) + return QPixmap.fromImage(image) + + def _get_address_text(self, asset, img_path): + """Get the appropriate address text based on asset type and network.""" + if not img_path: + return asset.asset_id + + network = SettingRepository.get_wallet_network() + network_token_map = { + NetworkEnumModel.REGTEST: TokenSymbol.REGTEST_BITCOIN, + NetworkEnumModel.TESTNET: TokenSymbol.TESTNET_BITCOIN, + NetworkEnumModel.TESTNET4: TokenSymbol.TESTNET4_BITCOIN, + } + return network_token_map.get(network, '') + + def _get_outbound_balance_text(self, asset, img_path): + """Get the outbound balance text for the asset.""" + if img_path: + return 'N/A' + + return str(asset.balance.offchain_outbound) if asset.balance.offchain_outbound else 'N/A' + + def _apply_bitcoin_naming(self, asset): + """Apply Bitcoin-specific naming conventions based on ticker.""" + if 'BTC' not in asset.ticker: + return + + self.token_symbol.setText(TokenSymbol.SAT.value) + bitcoin_asset = AssetType.BITCOIN.value.lower() + + # For mainnet Bitcoin + if asset.ticker == TokenSymbol.BITCOIN.value: + self.asset_name.setText(bitcoin_asset) + return + + # For testnet variants and regtest, use network to distinguish + # since TESTNET_BITCOIN and TESTNET4_BITCOIN have the same ticker 'tBTC' + network = SettingRepository.get_wallet_network() + network_naming_map = { + NetworkEnumModel.TESTNET: f'{NetworkEnumModel.TESTNET.value} {bitcoin_asset}', + NetworkEnumModel.TESTNET4: f'{NetworkEnumModel.TESTNET4.value} {bitcoin_asset}', + NetworkEnumModel.REGTEST: f'{NetworkEnumModel.REGTEST.value} {bitcoin_asset}', + } + + if network in network_naming_map: + self.asset_name.setText(network_naming_map[network]) + def create_fungible_card(self, asset, img_path=None): """This method creates all the fungible assets elements of the main asset page.""" self.fungible_frame = ClickableFrame( - asset.asset_id, asset.name, self.fungibles_widget, asset_type=asset.asset_iface, + asset.asset_id, asset.name, self.fungibles_widget, asset_type=AssetType.BITCOIN if img_path else AssetType.NIA, ) self.fungible_frame.setStyleSheet( load_stylesheet('views/qss/fungible_asset_style.qss'), @@ -298,23 +353,16 @@ def create_fungible_card(self, asset, img_path=None): 'horizontal_layout_7', ) self.grid_layout_fungible_frame.setContentsMargins(6, 0, 6, 0) + + # Asset Logo self.asset_logo = QLabel(self.fungible_frame) self.asset_logo.setObjectName('asset_logo') - self.asset_logo.setMinimumSize(QSize(40, 40)) self.asset_logo.setMaximumSize(QSize(40, 40)) - - if img_path: - self.asset_logo.setPixmap(QPixmap(img_path)) - - else: - img_str = generate_identicon(asset.asset_id) - image = QImage.fromData(QByteArray.fromBase64(img_str.encode())) - pixmap = QPixmap.fromImage(image) - self.asset_logo.setPixmap(pixmap) - + self.asset_logo.setPixmap(self._get_asset_logo_pixmap(asset, img_path)) self.grid_layout_fungible_frame.addWidget(self.asset_logo, 0, 0) + # Asset Name self.asset_name = QLabel(self.fungible_frame) self.asset_name.setObjectName('asset_name') self.asset_name.setMinimumSize(QSize(135, 40)) @@ -326,31 +374,21 @@ def create_fungible_card(self, asset, img_path=None): self.asset_name.setText(asset.name) self.grid_layout_fungible_frame.addWidget(self.asset_name, 0, 1) + # Address self.address = QLabel(self.fungible_frame) self.address.setObjectName('address') self.address.setMinimumSize(QSize(600, 0)) self.address.setMaximumSize(QSize(16777215, 16777215)) - self.address.setStyleSheet( - 'padding-left:10px;', - ) - - if asset.asset_iface == AssetType.BITCOIN: - network = SettingRepository.get_wallet_network() - if network == NetworkEnumModel.REGTEST: - self.address.setText(TokenSymbol.REGTEST_BITCOIN) - elif network == NetworkEnumModel.TESTNET: - self.address.setText(TokenSymbol.TESTNET_BITCOIN) - else: - self.address.setText(asset.asset_id) - + self.address.setStyleSheet('padding-left:10px;') + self.address.setText(self._get_address_text(asset, img_path)) self.grid_layout_fungible_frame.addWidget( self.address, 0, 2, Qt.AlignLeft, ) + # Amount self.amount = QLabel(self.fungible_frame) self.amount.setObjectName('amount') self.amount.setMinimumSize(QSize(100, 40)) - self.amount.setText(str(asset.balance.future)) self.grid_layout_fungible_frame.addWidget( self.amount, 0, 3, Qt.AlignLeft, @@ -360,21 +398,16 @@ def create_fungible_card(self, asset, img_path=None): self.outbound_balance = QLabel(self.fungible_frame) self.outbound_balance.setObjectName('outbound_balance') self.outbound_balance.setMinimumSize(QSize(80, 40)) - - if asset.asset_iface == AssetType.RGB20: - self.outbound_balance.setText( - str(asset.balance.offchain_outbound) if asset.balance.offchain_outbound else 'N/A', - ) - else: - # Fallback for other asset types - self.outbound_balance.setText('N/A') + self.outbound_balance.setText( + self._get_outbound_balance_text(asset, img_path), + ) self.grid_layout_fungible_frame.addWidget( self.outbound_balance, 0, 4, Qt.AlignLeft, ) + # Token Symbol self.token_symbol = QLabel(self.fungible_frame) self.token_symbol.setObjectName('token_symbol') - self.token_symbol.setText(asset.ticker) self.grid_layout_fungible_frame.addWidget( self.token_symbol, 0, 5, Qt.AlignLeft, @@ -384,19 +417,8 @@ def create_fungible_card(self, asset, img_path=None): self.grid_layout_fungible_frame, ) - if 'BTC' in asset.ticker: - self.token_symbol.setText(TokenSymbol.SAT.value) - bitcoin_asset = AssetType.BITCOIN.value.lower() - if asset.ticker == TokenSymbol.BITCOIN.value: - self.asset_name.setText(bitcoin_asset) - if asset.ticker == TokenSymbol.TESTNET_BITCOIN.value: - self.asset_name.setText( - f'{NetworkEnumModel.TESTNET.value} {bitcoin_asset}', - ) - if asset.ticker == TokenSymbol.REGTEST_BITCOIN.value: - self.asset_name.setText( - f'{NetworkEnumModel.REGTEST.value} {bitcoin_asset}', - ) + # Apply Bitcoin-specific naming + self._apply_bitcoin_naming(asset) self.vertical_layout_3.addWidget(self.fungible_frame) self.fungible_frame.clicked.connect(self.handle_asset_frame_click) @@ -411,7 +433,7 @@ def setup_ui_connection(self): ) self.title_frame.action_button.clicked.connect( lambda: self._view_model.main_asset_view_model.navigate_issue_asset( - self._view_model.page_navigation.issue_rgb20_asset_page, + self._view_model.page_navigation.issue_nia_asset_page, ), ) self._view_model.main_asset_view_model.loading_started.connect( @@ -445,10 +467,10 @@ def handle_asset_frame_click(self, asset_id, asset_name, image_path, asset_type) if asset_type == AssetType.BITCOIN.value: self._view_model.page_navigation.bitcoin_page() else: - self._view_model.rgb25_view_model.asset_info.emit( + self._view_model.cfa_view_model.asset_info.emit( asset_id, asset_name, image_path, asset_type, ) - self._view_model.page_navigation.rgb25_detail_page( + self._view_model.page_navigation.cfa_detail_page( RgbAssetPageLoadModel(asset_type=asset_type), ) diff --git a/src/views/ui_issue_rgb25.py b/src/views/ui_issue_cfa.py similarity index 68% rename from src/views/ui_issue_rgb25.py rename to src/views/ui_issue_cfa.py index f4002cac..cb7eff72 100644 --- a/src/views/ui_issue_rgb25.py +++ b/src/views/ui_issue_cfa.py @@ -1,6 +1,6 @@ # pylint: disable=too-many-instance-attributes, too-many-statements, unused-import -"""This module contains the IssueRGB25Widget class, - which represents the UI for issuing RGB25 assets. +"""This module contains the IssueCFAWidget class, + which represents the UI for issuing CFA assets. """ from __future__ import annotations @@ -23,12 +23,12 @@ from PySide6.QtWidgets import QWidget import src.resources_rc -from accessible_constant import ISSUE_RGB25_ASSET_CLOSE_BUTTON -from accessible_constant import ISSUE_RGB25_BUTTON -from accessible_constant import RGB25_ASSET_AMOUNT -from accessible_constant import RGB25_ASSET_DESCRIPTION -from accessible_constant import RGB25_ASSET_NAME -from accessible_constant import RGB25_UPLOAD_FILE_BUTTON +from accessible_constant import CFA_ASSET_AMOUNT +from accessible_constant import CFA_ASSET_DESCRIPTION +from accessible_constant import CFA_ASSET_NAME +from accessible_constant import CFA_UPLOAD_FILE_BUTTON +from accessible_constant import ISSUE_CFA_ASSET_CLOSE_BUTTON +from accessible_constant import ISSUE_CFA_BUTTON from src.model.common_operation_model import NodeInfoResponseModel from src.model.node_info_model import NodeInfoModel from src.model.success_model import SuccessPageModel @@ -43,14 +43,14 @@ from src.views.components.wallet_logo_frame import WalletLogoFrame -class IssueRGB25Widget(QWidget): - """This class represents the UI for issuing RGB25 assets.""" +class IssueCFAWidget(QWidget): + """This class represents the UI for issuing CFA assets.""" def __init__(self, view_model: MainViewModel): - """Initialize the IssueRGB25Widget class.""" + """Initialize the IssueCFAWidget class.""" super().__init__() - self.render_timer = RenderTimer(task_name='IssueRGB25Asset Rendering') - self.setStyleSheet(load_stylesheet('views/qss/issue_rgb25_style.qss')) + self.render_timer = RenderTimer(task_name='IssueCFAAsset Rendering') + self.setStyleSheet(load_stylesheet('views/qss/issue_cfa_style.qss')) self._view_model: MainViewModel = view_model self.grid_layout = QGridLayout(self) self.grid_layout.setObjectName('gridLayout') @@ -64,11 +64,11 @@ def __init__(self, view_model: MainViewModel): self.grid_layout.addItem(self.vertical_spacer, 0, 2, 1, 1) - self.issue_rgb_25_card = QWidget(self) - self.issue_rgb_25_card.setObjectName('issue_rgb_25_card') - self.issue_rgb_25_card.setMinimumSize(QSize(499, 608)) - self.issue_rgb_25_card.setMaximumSize(QSize(499, 608)) - self.grid_layout_1 = QGridLayout(self.issue_rgb_25_card) + self.issue_cfa_card = QWidget(self) + self.issue_cfa_card.setObjectName('issue_cfa_card') + self.issue_cfa_card.setMinimumSize(QSize(499, 608)) + self.issue_cfa_card.setMaximumSize(QSize(499, 608)) + self.grid_layout_1 = QGridLayout(self.issue_cfa_card) self.grid_layout_1.setObjectName('gridLayout_26') self.grid_layout_1.setContentsMargins(1, -1, 1, 35) self.horizontal_spacer_1 = QSpacerItem( @@ -77,16 +77,16 @@ def __init__(self, view_model: MainViewModel): self.grid_layout_1.addItem(self.horizontal_spacer_1, 6, 0) - self.issue_rgb25_button = PrimaryButton() - self.issue_rgb25_button.setAccessibleName(ISSUE_RGB25_BUTTON) - self.issue_rgb25_button.setMinimumSize(QSize(402, 40)) - self.issue_rgb25_button.setMaximumSize(QSize(402, 40)) + self.issue_cfa_button = PrimaryButton() + self.issue_cfa_button.setAccessibleName(ISSUE_CFA_BUTTON) + self.issue_cfa_button.setMinimumSize(QSize(402, 40)) + self.issue_cfa_button.setMaximumSize(QSize(402, 40)) self.grid_layout_1.addWidget( - self.issue_rgb25_button, 7, 0, Qt.AlignCenter, + self.issue_cfa_button, 7, 0, Qt.AlignCenter, ) - self.header_line = QFrame(self.issue_rgb_25_card) + self.header_line = QFrame(self.issue_cfa_card) self.header_line.setObjectName('line_top') self.header_line.setFrameShape(QFrame.Shape.HLine) @@ -94,63 +94,63 @@ def __init__(self, view_model: MainViewModel): self.grid_layout_1.addWidget(self.header_line, 1, 0, 1, 1) - self.issue_rgb25_details_layout = QVBoxLayout() - self.issue_rgb25_details_layout.setSpacing(12) - self.issue_rgb25_details_layout.setObjectName('vertical_layout_6') - self.issue_rgb25_details_layout.setContentsMargins(45, 15, 45, -1) - self.asset_name_label = QLabel(self.issue_rgb_25_card) + self.issue_cfa_details_layout = QVBoxLayout() + self.issue_cfa_details_layout.setSpacing(12) + self.issue_cfa_details_layout.setObjectName('vertical_layout_6') + self.issue_cfa_details_layout.setContentsMargins(45, 15, 45, -1) + self.asset_name_label = QLabel(self.issue_cfa_card) self.asset_name_label.setObjectName('asset_name_label') self.asset_name_label.setAutoFillBackground(False) self.asset_name_label.setFrameShadow(QFrame.Plain) self.asset_name_label.setLineWidth(1) - self.issue_rgb25_details_layout.addWidget(self.asset_name_label) + self.issue_cfa_details_layout.addWidget(self.asset_name_label) - self.name_of_the_asset_input = QLineEdit(self.issue_rgb_25_card) + self.name_of_the_asset_input = QLineEdit(self.issue_cfa_card) self.name_of_the_asset_input.setObjectName('name_of_the_asset_input') - self.name_of_the_asset_input.setAccessibleName(RGB25_ASSET_NAME) + self.name_of_the_asset_input.setAccessibleName(CFA_ASSET_NAME) self.name_of_the_asset_input.setMinimumSize(QSize(403, 40)) self.name_of_the_asset_input.setMaximumSize(QSize(403, 16777215)) self.name_of_the_asset_input.setClearButtonEnabled(True) - self.issue_rgb25_details_layout.addWidget(self.name_of_the_asset_input) + self.issue_cfa_details_layout.addWidget(self.name_of_the_asset_input) - self.asset_description_label = QLabel(self.issue_rgb_25_card) + self.asset_description_label = QLabel(self.issue_cfa_card) self.asset_description_label.setObjectName('asset_description_label') - self.asset_description_input = QLineEdit(self.issue_rgb_25_card) + self.asset_description_input = QLineEdit(self.issue_cfa_card) self.asset_description_input.setObjectName('asset_description_input') - self.asset_description_input.setAccessibleName(RGB25_ASSET_DESCRIPTION) + self.asset_description_input.setAccessibleName(CFA_ASSET_DESCRIPTION) self.asset_description_input.setMinimumSize(QSize(403, 40)) self.asset_description_input.setMaximumSize(QSize(403, 16777215)) self.asset_description_input.setFrame(False) self.asset_description_input.setClearButtonEnabled(True) - self.issue_rgb25_details_layout.addWidget(self.asset_description_label) + self.issue_cfa_details_layout.addWidget(self.asset_description_label) - self.issue_rgb25_details_layout.addWidget(self.asset_description_input) - self.total_supply_label = QLabel(self.issue_rgb_25_card) + self.issue_cfa_details_layout.addWidget(self.asset_description_input) + self.total_supply_label = QLabel(self.issue_cfa_card) self.total_supply_label.setObjectName('total_supply_label') - self.issue_rgb25_details_layout.addWidget(self.total_supply_label) + self.issue_cfa_details_layout.addWidget(self.total_supply_label) - self.amount_input = QLineEdit(self.issue_rgb_25_card) + self.amount_input = QLineEdit(self.issue_cfa_card) self.amount_input.setObjectName('amount_input') - self.amount_input.setAccessibleName(RGB25_ASSET_AMOUNT) + self.amount_input.setAccessibleName(CFA_ASSET_AMOUNT) self.amount_input.setMinimumSize(QSize(403, 40)) self.amount_input.setMaximumSize(QSize(403, 40)) self.amount_input.setClearButtonEnabled(True) set_number_validator(self.amount_input) - self.issue_rgb25_details_layout.addWidget(self.amount_input) + self.issue_cfa_details_layout.addWidget(self.amount_input) self.vertical_spacer_3 = QSpacerItem( 20, 30, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding, ) - self.issue_rgb25_details_layout.addItem(self.vertical_spacer_3) + self.issue_cfa_details_layout.addItem(self.vertical_spacer_3) - self.form_divider_line = QFrame(self.issue_rgb_25_card) + self.form_divider_line = QFrame(self.issue_cfa_card) self.form_divider_line.setObjectName('line_bottom') self.form_divider_line.setMinimumSize(QSize(403, 1)) self.form_divider_line.setMaximumSize(QSize(403, 1)) @@ -158,7 +158,7 @@ def __init__(self, view_model: MainViewModel): self.form_divider_line.setFrameShape(QFrame.Shape.HLine) self.form_divider_line.setFrameShadow(QFrame.Shadow.Sunken) - self.issue_rgb25_details_layout.addWidget( + self.issue_cfa_details_layout.addWidget( self.form_divider_line, 0, Qt.AlignHCenter, ) @@ -166,18 +166,18 @@ def __init__(self, view_model: MainViewModel): 20, 20, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding, ) - self.issue_rgb25_details_layout.addItem(self.vertical_spacer_4) + self.issue_cfa_details_layout.addItem(self.vertical_spacer_4) - self.asset_file = QLabel(self.issue_rgb_25_card) + self.asset_file = QLabel(self.issue_cfa_card) self.asset_file.setObjectName('asset_file') self.asset_file.setMinimumSize(QSize(403, 17)) self.asset_file.setMaximumSize(QSize(403, 16777215)) - self.issue_rgb25_details_layout.addWidget(self.asset_file) + self.issue_cfa_details_layout.addWidget(self.asset_file) - self.upload_file = QPushButton(self.issue_rgb_25_card) + self.upload_file = QPushButton(self.issue_cfa_card) self.upload_file.setObjectName('upload_file') - self.upload_file.setAccessibleName(RGB25_UPLOAD_FILE_BUTTON) + self.upload_file.setAccessibleName(CFA_UPLOAD_FILE_BUTTON) self.upload_file.setMinimumSize(QSize(403, 40)) self.upload_file.setMaximumSize(QSize(403, 40)) self.upload_file.setAcceptDrops(False) @@ -190,59 +190,59 @@ def __init__(self, view_model: MainViewModel): icon.addFile(':/assets/upload.png', QSize(), QIcon.Normal, QIcon.Off) self.upload_file.setIcon(icon) - self.issue_rgb25_details_layout.addWidget( + self.issue_cfa_details_layout.addWidget( self.upload_file, 0, Qt.AlignHCenter, ) - self.file_path = QLabel(self.issue_rgb_25_card) + self.file_path = QLabel(self.issue_cfa_card) self.file_path.setObjectName('asset_ti') - self.issue_rgb25_details_layout.addWidget( + self.issue_cfa_details_layout.addWidget( self.file_path, 0, Qt.AlignHCenter, ) self.vertical_spacer_5 = QSpacerItem( 20, 70, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred, ) - self.issue_rgb25_details_layout.addItem(self.vertical_spacer_5) + self.issue_cfa_details_layout.addItem(self.vertical_spacer_5) self.grid_layout_1.addLayout( - self.issue_rgb25_details_layout, 2, 0, 1, 1, + self.issue_cfa_details_layout, 2, 0, 1, 1, ) self.grid_layout_2 = QGridLayout() self.grid_layout_2.setSpacing(0) self.grid_layout_2.setObjectName('grid_layout_2') self.grid_layout_2.setContentsMargins(36, 8, 40, -1) - self.issue_rgb_25_asset_title_label = QLabel(self.issue_rgb_25_card) - self.issue_rgb_25_asset_title_label.setObjectName( - 'issue_rgb_25_asset_title_label', + self.issue_cfa_asset_title_label = QLabel(self.issue_cfa_card) + self.issue_cfa_asset_title_label.setObjectName( + 'issue_cfa_asset_title_label', ) self.grid_layout_2.addWidget( - self.issue_rgb_25_asset_title_label, 0, 0, 1, 1, + self.issue_cfa_asset_title_label, 0, 0, 1, 1, ) - self.rgb_25_close_btn = QPushButton(self.issue_rgb_25_card) - self.rgb_25_close_btn.setObjectName('rgb_25_close_btn') - self.rgb_25_close_btn.setAccessibleName(ISSUE_RGB25_ASSET_CLOSE_BUTTON) - self.rgb_25_close_btn.setMinimumSize(QSize(24, 24)) - self.rgb_25_close_btn.setMaximumSize(QSize(50, 65)) - self.rgb_25_close_btn.setAutoFillBackground(False) + self.cfa_close_btn = QPushButton(self.issue_cfa_card) + self.cfa_close_btn.setObjectName('cfa_close_btn') + self.cfa_close_btn.setAccessibleName(ISSUE_CFA_ASSET_CLOSE_BUTTON) + self.cfa_close_btn.setMinimumSize(QSize(24, 24)) + self.cfa_close_btn.setMaximumSize(QSize(50, 65)) + self.cfa_close_btn.setAutoFillBackground(False) icon1 = QIcon() icon1.addFile( ':/assets/x_circle.png', QSize(), QIcon.Normal, QIcon.Off, ) - self.rgb_25_close_btn.setIcon(icon1) - self.rgb_25_close_btn.setIconSize(QSize(24, 24)) - self.rgb_25_close_btn.setCheckable(False) - self.rgb_25_close_btn.setChecked(False) - self.rgb_25_close_btn.setCursor( + self.cfa_close_btn.setIcon(icon1) + self.cfa_close_btn.setIconSize(QSize(24, 24)) + self.cfa_close_btn.setCheckable(False) + self.cfa_close_btn.setChecked(False) + self.cfa_close_btn.setCursor( QCursor(Qt.CursorShape.PointingHandCursor), ) - self.grid_layout_2.addWidget(self.rgb_25_close_btn, 0, 1, 1, 1) + self.grid_layout_2.addWidget(self.cfa_close_btn, 0, 1, 1, 1) self.grid_layout_1.addLayout(self.grid_layout_2, 0, 0, 1, 1) @@ -258,7 +258,7 @@ def __init__(self, view_model: MainViewModel): self.grid_layout_1.addItem(self.vertical_spacer_7, 7, 0, 1, 1) - self.footer_line = QFrame(self.issue_rgb_25_card) + self.footer_line = QFrame(self.issue_cfa_card) self.footer_line.setObjectName('line_6') self.footer_line.setMinimumSize(QSize(498, 1)) @@ -273,7 +273,7 @@ def __init__(self, view_model: MainViewModel): self.grid_layout_1.addItem(self.vertical_spacer_8, 4, 0, 1, 1) - self.grid_layout.addWidget(self.issue_rgb_25_card, 1, 1, 2, 2) + self.grid_layout.addWidget(self.issue_cfa_card, 1, 1, 2, 2) self.horizontal_spacer_2 = QSpacerItem( 301, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum, @@ -292,13 +292,13 @@ def __init__(self, view_model: MainViewModel): ) self.grid_layout.addItem(self.vertical_spacer_2, 3, 1, 1, 1) - self.issue_rgb25_button.setDisabled(True) + self.issue_cfa_button.setDisabled(True) self.retranslate_ui() self.setup_ui_connections() def retranslate_ui(self): """Retranslate the UI elements.""" - self.issue_rgb25_button.setText( + self.issue_cfa_button.setText( QCoreApplication.translate( IRIS_WALLET_TRANSLATIONS_CONTEXT, 'issue_asset', None, ), @@ -343,23 +343,23 @@ def retranslate_ui(self): IRIS_WALLET_TRANSLATIONS_CONTEXT, 'upload_file', None, ), ) - self.issue_rgb_25_asset_title_label.setText( + self.issue_cfa_asset_title_label.setText( QCoreApplication.translate( - IRIS_WALLET_TRANSLATIONS_CONTEXT, 'issue_new_rgb25_asset', None, + IRIS_WALLET_TRANSLATIONS_CONTEXT, 'issue_new_cfa_asset', None, ), ) def setup_ui_connections(self): """Set up connections for UI elements.""" - self.rgb_25_close_btn.clicked.connect( + self.cfa_close_btn.clicked.connect( self.on_close, ) - self.issue_rgb25_button.clicked.connect(self.on_issue_rgb25) + self.issue_cfa_button.clicked.connect(self.on_issue_cfa) self.upload_file.clicked.connect(self.on_upload_asset_file) - self._view_model.issue_rgb25_asset_view_model.is_loading.connect( + self._view_model.issue_cfa_asset_view_model.is_loading.connect( self.update_loading_state, ) - self._view_model.issue_rgb25_asset_view_model.file_upload_message.connect( + self._view_model.issue_cfa_asset_view_model.file_upload_message.connect( self.show_file_preview, ) self.amount_input.textChanged.connect(self.handle_button_enabled) @@ -369,7 +369,7 @@ def setup_ui_connections(self): self.name_of_the_asset_input.textChanged.connect( self.handle_button_enabled, ) - self._view_model.issue_rgb25_asset_view_model.success_page_message.connect( + self._view_model.issue_cfa_asset_view_model.success_page_message.connect( self.show_asset_issued, ) self.amount_input.textChanged.connect( @@ -387,11 +387,11 @@ def show_file_preview(self, file_upload_message): IRIS_WALLET_TRANSLATIONS_CONTEXT, 'image_validation', None, ).format(get_max_file_size.max_media_upload_size_mb) self.file_path.setText(validation_text) - self.issue_rgb25_button.setDisabled(True) - self.issue_rgb_25_card.setMaximumSize(QSize(499, 608)) + self.issue_cfa_button.setDisabled(True) + self.issue_cfa_card.setMaximumSize(QSize(499, 608)) else: self.file_path.setText(file_upload_message) - self.issue_rgb_25_card.setMaximumSize(QSize(499, 808)) + self.issue_cfa_card.setMaximumSize(QSize(499, 808)) pixmap = resize_image(file_upload_message, 242, 242) self.file_path.setPixmap( QPixmap(pixmap), @@ -401,20 +401,20 @@ def show_file_preview(self, file_upload_message): IRIS_WALLET_TRANSLATIONS_CONTEXT, 'change_uploaded_file', 'CHANGE UPLOADED FILE', ), ) - self.issue_rgb25_button.setDisabled(False) + self.issue_cfa_button.setDisabled(False) - def on_issue_rgb25(self): - """Issue rgb25 while issue rgb25 button clicked""" + def on_issue_cfa(self): + """Issue cfa while issue cfa button clicked""" asset_description = self.asset_description_input.text() asset_name = self.name_of_the_asset_input.text() total_supply = self.amount_input.text() - self._view_model.issue_rgb25_asset_view_model.issue_rgb25_asset( + self._view_model.issue_cfa_asset_view_model.issue_cfa_asset( asset_description, asset_name, total_supply, ) def on_upload_asset_file(self): """This method handled upload asset file operation.""" - self._view_model.issue_rgb25_asset_view_model.open_file_dialog() + self._view_model.issue_cfa_asset_view_model.open_file_dialog() def on_close(self): """Navigate to the collectibles page.""" @@ -423,9 +423,9 @@ def on_close(self): def handle_button_enabled(self): """Updates the enabled state of the send button.""" if (self.amount_input.text() and self.asset_description_input.text() and self.name_of_the_asset_input.text() and self.amount_input.text() != '0'): - self.issue_rgb25_button.setDisabled(False) + self.issue_cfa_button.setDisabled(False) else: - self.issue_rgb25_button.setDisabled(True) + self.issue_cfa_button.setDisabled(True) def update_loading_state(self, is_loading: bool): """ @@ -436,11 +436,11 @@ def update_loading_state(self, is_loading: bool): """ if is_loading: self.render_timer.start() - self.issue_rgb25_button.start_loading() - self.rgb_25_close_btn.setDisabled(True) + self.issue_cfa_button.start_loading() + self.cfa_close_btn.setDisabled(True) else: - self.issue_rgb25_button.stop_loading() - self.rgb_25_close_btn.setDisabled(False) + self.issue_cfa_button.stop_loading() + self.cfa_close_btn.setDisabled(False) def show_asset_issued(self, asset_name): """This method handled after the asset issue""" diff --git a/src/views/ui_issue_rgb20.py b/src/views/ui_issue_nia.py similarity index 62% rename from src/views/ui_issue_rgb20.py rename to src/views/ui_issue_nia.py index 2b997de0..989b3f2a 100644 --- a/src/views/ui_issue_rgb20.py +++ b/src/views/ui_issue_nia.py @@ -1,6 +1,6 @@ # pylint: disable=too-many-instance-attributes, too-many-statements, unused-import -"""This module contains the IssueRGB20Widget class, - which represents the UI for issuing RGB20 assets. +"""This module contains the IssueNIAWidget class, + which represents the UI for issuing NIA assets. """ from __future__ import annotations @@ -21,11 +21,11 @@ from PySide6.QtWidgets import QWidget import src.resources_rc -from accessible_constant import ISSUE_RGB20_ASSET_CLOSE_BUTTON -from accessible_constant import ISSUE_RGB20_BUTTON -from accessible_constant import RGB20_ASSET_AMOUNT -from accessible_constant import RGB20_ASSET_NAME -from accessible_constant import RGB20_ASSET_TICKER +from accessible_constant import ISSUE_NIA_ASSET_CLOSE_BUTTON +from accessible_constant import ISSUE_NIA_BUTTON +from accessible_constant import NIA_ASSET_AMOUNT +from accessible_constant import NIA_ASSET_NAME +from accessible_constant import NIA_ASSET_TICKER from src.model.success_model import SuccessPageModel from src.utils.common_utils import set_number_validator from src.utils.common_utils import set_placeholder_value @@ -37,43 +37,43 @@ from src.views.components.wallet_logo_frame import WalletLogoFrame -class IssueRGB20Widget(QWidget): - """This class represents the UI for issuing RGB20 assets.""" +class IssueNIAWidget(QWidget): + """This class represents the UI for issuing NIA assets.""" def __init__(self, view_model): super().__init__() - self.render_timer = RenderTimer(task_name='IssueRGB20Asset Rendering') + self.render_timer = RenderTimer(task_name='IssueNIAAsset Rendering') self._view_model: MainViewModel = view_model - self.setStyleSheet(load_stylesheet('views/qss/issue_rgb20_style.qss')) - self.setObjectName('issue_rgb20_page') - self.issue_rgb20_grid_layout = QGridLayout(self) - self.issue_rgb20_grid_layout.setObjectName('issue_rgb20_grid_layout') - self.issue_rgb20_wallet_logo = WalletLogoFrame(self) + self.setStyleSheet(load_stylesheet('views/qss/issue_nia_style.qss')) + self.setObjectName('issue_nia_page') + self.issue_nia_grid_layout = QGridLayout(self) + self.issue_nia_grid_layout.setObjectName('issue_nia_grid_layout') + self.issue_nia_wallet_logo = WalletLogoFrame(self) - self.issue_rgb20_grid_layout.addWidget( - self.issue_rgb20_wallet_logo, 0, 0, 1, 2, + self.issue_nia_grid_layout.addWidget( + self.issue_nia_wallet_logo, 0, 0, 1, 2, ) - self.horizontal_spacer_rgb20_widget = QSpacerItem( + self.horizontal_spacer_nia_widget = QSpacerItem( 265, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum, ) - self.issue_rgb20_grid_layout.addItem( - self.horizontal_spacer_rgb20_widget, 1, 3, 1, 1, + self.issue_nia_grid_layout.addItem( + self.horizontal_spacer_nia_widget, 1, 3, 1, 1, ) - self.vertical_spacer_rgb20_widget = QSpacerItem( + self.vertical_spacer_nia_widget = QSpacerItem( 20, 190, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding, ) - self.issue_rgb20_grid_layout.addItem( - self.vertical_spacer_rgb20_widget, 3, 1, 1, 1, + self.issue_nia_grid_layout.addItem( + self.vertical_spacer_nia_widget, 3, 1, 1, 1, ) self.horizontal_spacer_2 = QSpacerItem( @@ -83,93 +83,93 @@ def __init__(self, view_model): QSizePolicy.Policy.Minimum, ) - self.issue_rgb20_grid_layout.addItem( + self.issue_nia_grid_layout.addItem( self.horizontal_spacer_2, 2, 0, 1, 1, ) - self.issue_rgb20_vertical_spacer_1 = QSpacerItem( + self.issue_nia_vertical_spacer_1 = QSpacerItem( 20, 190, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding, ) - self.issue_rgb20_grid_layout.addItem( - self.issue_rgb20_vertical_spacer_1, 0, 2, 1, 1, + self.issue_nia_grid_layout.addItem( + self.issue_nia_vertical_spacer_1, 0, 2, 1, 1, ) - self.issue_rgb_20_widget = QWidget(self) - self.issue_rgb_20_widget.setObjectName( - 'issue_rgb20_widget', + self.issue_nia_widget = QWidget(self) + self.issue_nia_widget.setObjectName( + 'issue_nia_widget', ) - self.issue_rgb_20_widget.setMinimumSize(QSize(499, 608)) - self.issue_rgb_20_widget.setMaximumSize(QSize(466, 608)) + self.issue_nia_widget.setMinimumSize(QSize(499, 608)) + self.issue_nia_widget.setMaximumSize(QSize(466, 608)) - self.inner_grid_layout = QGridLayout(self.issue_rgb_20_widget) + self.inner_grid_layout = QGridLayout(self.issue_nia_widget) self.inner_grid_layout.setSpacing(6) self.inner_grid_layout.setObjectName('inner_grid_layout') self.inner_grid_layout.setContentsMargins(1, 4, 1, 30) - self.vertical_layout_issue_rgb20 = QVBoxLayout() - self.vertical_layout_issue_rgb20.setSpacing(6) - self.vertical_layout_issue_rgb20.setObjectName( + self.vertical_layout_issue_nia = QVBoxLayout() + self.vertical_layout_issue_nia.setSpacing(6) + self.vertical_layout_issue_nia.setObjectName( 'vertical_layout_setup_wallet_password', ) - self.issue_rgb20_title_layout = QHBoxLayout() - self.issue_rgb20_title_layout.setObjectName('horizontal_layout_1') - self.issue_rgb20_title_layout.setContentsMargins(35, 9, 40, 0) - self.issue_rgb20_title = QLabel( - self.issue_rgb_20_widget, + self.issue_nia_title_layout = QHBoxLayout() + self.issue_nia_title_layout.setObjectName('horizontal_layout_1') + self.issue_nia_title_layout.setContentsMargins(35, 9, 40, 0) + self.issue_nia_title = QLabel( + self.issue_nia_widget, ) - self.issue_rgb20_title.setObjectName( + self.issue_nia_title.setObjectName( 'set_wallet_password_label', ) - self.issue_rgb20_title.setMinimumSize(QSize(415, 63)) + self.issue_nia_title.setMinimumSize(QSize(415, 63)) - self.issue_rgb20_title_layout.addWidget(self.issue_rgb20_title) + self.issue_nia_title_layout.addWidget(self.issue_nia_title) - self.rgb_20_close_btn = QPushButton(self.issue_rgb_20_widget) - self.rgb_20_close_btn.setAccessibleName(ISSUE_RGB20_ASSET_CLOSE_BUTTON) - self.rgb_20_close_btn.setObjectName('rgb_20_close_btn') - self.rgb_20_close_btn.setMinimumSize(QSize(24, 24)) - self.rgb_20_close_btn.setMaximumSize(QSize(50, 65)) - self.rgb_20_close_btn.setAutoFillBackground(False) - self.rgb_20_close_btn.setCursor( + self.nia_close_btn = QPushButton(self.issue_nia_widget) + self.nia_close_btn.setAccessibleName(ISSUE_NIA_ASSET_CLOSE_BUTTON) + self.nia_close_btn.setObjectName('nia_close_btn') + self.nia_close_btn.setMinimumSize(QSize(24, 24)) + self.nia_close_btn.setMaximumSize(QSize(50, 65)) + self.nia_close_btn.setAutoFillBackground(False) + self.nia_close_btn.setCursor( QCursor(Qt.CursorShape.PointingHandCursor), ) - issue_rgb20_close_icon = QIcon() - issue_rgb20_close_icon.addFile( + issue_nia_close_icon = QIcon() + issue_nia_close_icon.addFile( ':/assets/x_circle.png', QSize(), QIcon.Normal, QIcon.Off, ) - self.rgb_20_close_btn.setIcon(issue_rgb20_close_icon) - self.rgb_20_close_btn.setIconSize(QSize(24, 24)) - self.rgb_20_close_btn.setCheckable(False) - self.rgb_20_close_btn.setChecked(False) + self.nia_close_btn.setIcon(issue_nia_close_icon) + self.nia_close_btn.setIconSize(QSize(24, 24)) + self.nia_close_btn.setCheckable(False) + self.nia_close_btn.setChecked(False) - self.issue_rgb20_title_layout.addWidget( - self.rgb_20_close_btn, 0, Qt.AlignHCenter, + self.issue_nia_title_layout.addWidget( + self.nia_close_btn, 0, Qt.AlignHCenter, ) - self.vertical_layout_issue_rgb20.addLayout( - self.issue_rgb20_title_layout, + self.vertical_layout_issue_nia.addLayout( + self.issue_nia_title_layout, ) - self.header_line = QFrame(self.issue_rgb_20_widget) + self.header_line = QFrame(self.issue_nia_widget) self.header_line.setObjectName('line_3') self.header_line.setFrameShape(QFrame.HLine) self.header_line.setFrameShadow(QFrame.Sunken) - self.vertical_layout_issue_rgb20.addWidget(self.header_line) + self.vertical_layout_issue_nia.addWidget(self.header_line) self.asset_ticker_layout = QVBoxLayout() self.asset_ticker_layout.setSpacing(0) self.asset_ticker_layout.setObjectName('vertical_layout_1') self.asset_ticker_layout.setContentsMargins(60, -1, 0, -1) - self.asset_ticker_label = QLabel(self.issue_rgb_20_widget) + self.asset_ticker_label = QLabel(self.issue_nia_widget) self.asset_ticker_label.setObjectName('asset_ticker_label') self.asset_ticker_label.setMinimumSize(QSize(0, 35)) self.asset_ticker_label.setBaseSize(QSize(0, 0)) @@ -180,10 +180,10 @@ def __init__(self, view_model): self.asset_ticker_layout.addWidget(self.asset_ticker_label) self.short_identifier_input = QLineEdit( - self.issue_rgb_20_widget, + self.issue_nia_widget, ) - self.short_identifier_input.setObjectName('issue_rgb20_input') - self.short_identifier_input.setAccessibleName(RGB20_ASSET_TICKER) + self.short_identifier_input.setObjectName('issue_nia_input') + self.short_identifier_input.setAccessibleName(NIA_ASSET_TICKER) self.short_identifier_input.setMinimumSize(QSize(0, 40)) self.short_identifier_input.setMaximumSize(QSize(370, 40)) @@ -192,7 +192,7 @@ def __init__(self, view_model): self.asset_ticker_layout.addWidget(self.short_identifier_input) - self.vertical_layout_issue_rgb20.addLayout( + self.vertical_layout_issue_nia.addLayout( self.asset_ticker_layout, ) @@ -201,17 +201,17 @@ def __init__(self, view_model): self.asset_name_layout.setObjectName('vertical_layout_2') self.asset_name_layout.setContentsMargins(60, -1, 0, -1) - self.asset_name_label = QLabel(self.issue_rgb_20_widget) + self.asset_name_label = QLabel(self.issue_nia_widget) self.asset_name_label.setObjectName('asset_name_label') self.asset_name_label.setMinimumSize(QSize(0, 40)) self.asset_name_label.setMaximumSize(QSize(370, 40)) self.asset_name_layout.addWidget(self.asset_name_label) self.asset_name_input = QLineEdit( - self.issue_rgb_20_widget, + self.issue_nia_widget, ) self.asset_name_input.setObjectName('asset_name_input') - self.asset_name_input.setAccessibleName(RGB20_ASSET_NAME) + self.asset_name_input.setAccessibleName(NIA_ASSET_NAME) self.asset_name_input.setMinimumSize(QSize(0, 40)) self.asset_name_input.setMaximumSize(QSize(370, 40)) @@ -220,7 +220,7 @@ def __init__(self, view_model): self.asset_name_layout.addWidget(self.asset_name_input) - self.vertical_layout_issue_rgb20.addLayout( + self.vertical_layout_issue_nia.addLayout( self.asset_name_layout, ) self.asset_supply_layout = QVBoxLayout() @@ -228,17 +228,17 @@ def __init__(self, view_model): self.asset_supply_layout.setObjectName('vertical_layout_3') self.asset_supply_layout.setContentsMargins(60, -1, 0, -1) - self.total_supply_label = QLabel(self.issue_rgb_20_widget) + self.total_supply_label = QLabel(self.issue_nia_widget) self.total_supply_label.setObjectName('total_supply_label') self.total_supply_label.setMinimumSize(QSize(0, 40)) self.total_supply_label.setMaximumSize(QSize(370, 40)) self.asset_supply_layout.addWidget(self.total_supply_label) self.amount_input = QLineEdit( - self.issue_rgb_20_widget, + self.issue_nia_widget, ) self.amount_input.setObjectName('amount_input') - self.amount_input.setAccessibleName(RGB20_ASSET_AMOUNT) + self.amount_input.setAccessibleName(NIA_ASSET_AMOUNT) self.amount_input.setMinimumSize(QSize(0, 40)) self.amount_input.setMaximumSize(QSize(370, 40)) set_number_validator(self.amount_input) @@ -247,58 +247,58 @@ def __init__(self, view_model): self.asset_supply_layout.addWidget(self.amount_input) - self.vertical_layout_issue_rgb20.addLayout( + self.vertical_layout_issue_nia.addLayout( self.asset_supply_layout, ) - self.vertical_spacer_issue_rgb20 = QSpacerItem( + self.vertical_spacer_issue_nia = QSpacerItem( 20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding, ) - self.vertical_layout_issue_rgb20.addItem( - self.vertical_spacer_issue_rgb20, + self.vertical_layout_issue_nia.addItem( + self.vertical_spacer_issue_nia, ) - self.footer_line = QFrame(self.issue_rgb_20_widget) + self.footer_line = QFrame(self.issue_nia_widget) self.footer_line.setObjectName('bottom_line_frame') self.footer_line.setFrameShape(QFrame.HLine) self.footer_line.setFrameShadow(QFrame.Sunken) - self.vertical_layout_issue_rgb20.addWidget(self.footer_line) + self.vertical_layout_issue_nia.addWidget(self.footer_line) self.issue_button_spacer = QSpacerItem( 20, 22, QSizePolicy.Preferred, QSizePolicy.Preferred, ) - self.vertical_layout_issue_rgb20.addItem(self.issue_button_spacer) - self.issue_rgb20_btn = PrimaryButton() - self.issue_rgb20_btn.setAccessibleName(ISSUE_RGB20_BUTTON) - self.issue_rgb20_btn.setCursor( + self.vertical_layout_issue_nia.addItem(self.issue_button_spacer) + self.issue_nia_btn = PrimaryButton() + self.issue_nia_btn.setAccessibleName(ISSUE_NIA_BUTTON) + self.issue_nia_btn.setCursor( QCursor(Qt.CursorShape.PointingHandCursor), ) - self.issue_rgb20_btn.setMinimumSize(QSize(402, 40)) - self.issue_rgb20_btn.setMaximumSize(QSize(402, 40)) + self.issue_nia_btn.setMinimumSize(QSize(402, 40)) + self.issue_nia_btn.setMaximumSize(QSize(402, 40)) - self.issue_rgb20_btn.setCursor( + self.issue_nia_btn.setCursor( QCursor(Qt.CursorShape.PointingHandCursor), ) - self.vertical_layout_issue_rgb20.addWidget( - self.issue_rgb20_btn, 0, Qt.AlignCenter, + self.vertical_layout_issue_nia.addWidget( + self.issue_nia_btn, 0, Qt.AlignCenter, ) self.inner_grid_layout.addLayout( - self.vertical_layout_issue_rgb20, + self.vertical_layout_issue_nia, 0, 0, 1, 1, ) - self.issue_rgb20_grid_layout.addWidget( - self.issue_rgb_20_widget, + self.issue_nia_grid_layout.addWidget( + self.issue_nia_widget, 1, 1, 2, @@ -315,14 +315,14 @@ def setup_ui_connection(self): self.handle_button_enabled, ) self.amount_input.textChanged.connect(self.handle_button_enabled) - self.rgb_20_close_btn.clicked.connect( - self._view_model.issue_rgb20_asset_view_model.on_close_click, + self.nia_close_btn.clicked.connect( + self._view_model.issue_nia_asset_view_model.on_close_click, ) - self._view_model.issue_rgb20_asset_view_model.issue_button_clicked.connect( + self._view_model.issue_nia_asset_view_model.issue_button_clicked.connect( self.update_loading_state, ) - self.issue_rgb20_btn.clicked.connect(self.on_issue_rgb20_click) - self._view_model.issue_rgb20_asset_view_model.is_issued.connect( + self.issue_nia_btn.clicked.connect(self.on_issue_nia_click) + self._view_model.issue_nia_asset_view_model.is_issued.connect( self.asset_issued, ) self.amount_input.textChanged.connect( @@ -331,11 +331,11 @@ def setup_ui_connection(self): def retranslate_ui(self): """Retranslate the UI elements.""" - self.issue_rgb20_btn.setDisabled(True) - self.issue_rgb20_title.setText( + self.issue_nia_btn.setDisabled(True) + self.issue_nia_title.setText( QCoreApplication.translate( IRIS_WALLET_TRANSLATIONS_CONTEXT, - 'issue_new_rgb20_asset', + 'issue_new_nia_asset', None, ), ) @@ -381,7 +381,7 @@ def retranslate_ui(self): None, ), ) - self.issue_rgb20_btn.setText( + self.issue_nia_btn.setText( QCoreApplication.translate( IRIS_WALLET_TRANSLATIONS_CONTEXT, 'issue_asset', None, ), @@ -389,28 +389,28 @@ def retranslate_ui(self): def update_loading_state(self, is_loading: bool): """ - Updates the loading state of the issue_rgb20_btn. + Updates the loading state of the issue_nia_btn. This method prints the loading state and starts or stops the loading animation of the proceed_wallet_password object based on the value of is_loading. """ if is_loading: self.render_timer.start() - self.issue_rgb20_btn.start_loading() - self.rgb_20_close_btn.setDisabled(True) + self.issue_nia_btn.start_loading() + self.nia_close_btn.setDisabled(True) else: self.render_timer.stop() - self.issue_rgb20_btn.stop_loading() - self.rgb_20_close_btn.setDisabled(False) + self.issue_nia_btn.stop_loading() + self.nia_close_btn.setDisabled(False) - def on_issue_rgb20_click(self): - """Handle the click event for issuing a new RGB20 asset.""" + def on_issue_nia_click(self): + """Handle the click event for issuing a new NIA asset.""" # Retrieve text values from input fields short_identifier = self.short_identifier_input.text().upper() asset_name = self.asset_name_input.text() amount_to_issue = self.amount_input.text() # Call the view model method and pass the text values as arguments - self._view_model.issue_rgb20_asset_view_model.on_issue_click( + self._view_model.issue_nia_asset_view_model.on_issue_click( short_identifier, asset_name, amount_to_issue, @@ -419,9 +419,9 @@ def on_issue_rgb20_click(self): def handle_button_enabled(self): """Updates the enabled state of the send button.""" if (self.short_identifier_input.text() and self.amount_input.text() and self.asset_name_input.text() and self.amount_input.text() != '0'): - self.issue_rgb20_btn.setDisabled(False) + self.issue_nia_btn.setDisabled(False) else: - self.issue_rgb20_btn.setDisabled(True) + self.issue_nia_btn.setDisabled(True) def asset_issued(self, asset_name): """This method handled after channel created""" diff --git a/src/views/ui_receive_rgb_asset.py b/src/views/ui_receive_rgb_asset.py index 8f9ad62c..a55f2cf6 100644 --- a/src/views/ui_receive_rgb_asset.py +++ b/src/views/ui_receive_rgb_asset.py @@ -1,6 +1,6 @@ # pylint: disable=too-many-instance-attributes, too-many-statements, unused-import """This module contains the ReceiveRGBAssetWidget class, - which represents the UI for receive rgb25. + which represents the UI for receive cfa. """ from __future__ import annotations @@ -38,8 +38,8 @@ def __init__(self, view_model, params: AssetDataModel): self.default_min_confirmation = SettingCardRepository.get_default_min_confirmation() self.receive_rgb_asset_page = ReceiveAssetWidget( self._view_model, - 'RGB25 page', - 'rgb25_address_info', + 'CFA page', + 'cfa_address_info', ) self.__loading_translucent_screen = LoadingTranslucentScreen( parent=self, description_text='Loading', dot_animation=True, @@ -54,9 +54,9 @@ def __init__(self, view_model, params: AssetDataModel): def generate_invoice(self): """Call get rgb invoice to get invoice""" if self.originating_page in [ - 'RGB20', + 'NIA', 'fungibles', - 'RGB25', + 'CFA', 'collectibles', 'channel_management', 'view_unspent_list', @@ -66,7 +66,7 @@ def generate_invoice(self): 'about', 'backup', ]: - self._view_model.receive_rgb25_view_model.get_rgb_invoice( + self._view_model.receive_cfa_view_model.get_rgb_invoice( self.default_min_confirmation.min_confirmation, self.asset_id, ) @@ -92,19 +92,19 @@ def setup_ui_connection(self): self.receive_rgb_asset_page.receive_asset_close_button.clicked.connect( self.close_button_navigation, ) - self._view_model.receive_rgb25_view_model.address.connect( + self._view_model.receive_cfa_view_model.address.connect( self.update_address, ) self._view_model.ln_offchain_view_model.invoice_get_event.connect( lambda address: self.update_address(address, ln_invoice=True), ) - self._view_model.receive_rgb25_view_model.message.connect( + self._view_model.receive_cfa_view_model.message.connect( self.handle_message, ) self._view_model.ln_offchain_view_model.invoice_get_event.connect( self.hide_loading_screen, ) - self._view_model.receive_rgb25_view_model.hide_loading.connect( + self._view_model.receive_cfa_view_model.hide_loading.connect( self.hide_loading_screen, ) @@ -113,16 +113,16 @@ def close_button_navigation(self): Navigate to the specified page when the close button is clicked. """ - if self.close_page_navigation == AssetType.RGB25.value: + if self.close_page_navigation == AssetType.CFA.value: self._view_model.page_navigation.collectibles_asset_page() - elif self.close_page_navigation == AssetType.RGB20.value: + elif self.close_page_navigation == AssetType.NIA.value: self._view_model.page_navigation.fungibles_asset_page() else: navigation_map = { - 'RGB20': self._view_model.page_navigation.fungibles_asset_page, + 'NIA': self._view_model.page_navigation.fungibles_asset_page, 'fungibles': self._view_model.page_navigation.fungibles_asset_page, 'create_invoice': self._view_model.page_navigation.fungibles_asset_page, - 'RGB25': self._view_model.page_navigation.collectibles_asset_page, + 'CFA': self._view_model.page_navigation.collectibles_asset_page, 'collectibles': self._view_model.page_navigation.collectibles_asset_page, 'channel_management': self._view_model.page_navigation.channel_management_page, 'view_unspent_list': self._view_model.page_navigation.view_unspent_list_page, diff --git a/src/views/ui_rgb_asset_detail.py b/src/views/ui_rgb_asset_detail.py index 96cb2818..25958ee9 100644 --- a/src/views/ui_rgb_asset_detail.py +++ b/src/views/ui_rgb_asset_detail.py @@ -4,8 +4,6 @@ """ from __future__ import annotations -import re - from PySide6.QtCore import QCoreApplication from PySide6.QtCore import QRect from PySide6.QtCore import QSize @@ -49,12 +47,11 @@ from src.model.selection_page_model import AssetDataModel from src.model.selection_page_model import SelectionPageModel from src.model.transaction_detail_page_model import TransactionDetailPageModel -from src.utils.common_utils import convert_hex_to_image from src.utils.common_utils import copy_text -from src.utils.common_utils import resize_image from src.utils.constant import IRIS_WALLET_TRANSLATIONS_CONTEXT from src.utils.helpers import load_stylesheet from src.utils.render_timer import RenderTimer +from src.utils.rgb_asset_helpers import handle_img_path from src.viewmodels.main_view_model import MainViewModel from src.views.components.buttons import AssetTransferButton from src.views.components.confirmation_dialog import ConfirmationDialog @@ -97,6 +94,7 @@ def __init__(self, view_model: MainViewModel, params: RgbAssetPageLoadModel): NetworkEnumModel.MAINNET.value: ':/assets/bitcoin.png', NetworkEnumModel.REGTEST.value: ':/assets/regtest_bitcoin.png', NetworkEnumModel.TESTNET.value: ':/assets/testnet_bitcoin.png', + NetworkEnumModel.TESTNET4.value: ':/assets/testnet_bitcoin.png', } self.__loading_translucent_screen = LoadingTranslucentScreen(self) self.asset_type = params.asset_type @@ -482,7 +480,7 @@ def navigate_to_selection_page(self, navigation): asset_id=self.asset_id_detail.toPlainText(), asset_name=self.asset_name, callback=navigation, - back_page_navigation=lambda: self._view_model.page_navigation.rgb25_detail_page( + back_page_navigation=lambda: self._view_model.page_navigation.cfa_detail_page( RgbAssetPageLoadModel(asset_type=self.asset_type), ), rgb_asset_page_load_model=rgb_asset_page_load_model, @@ -496,7 +494,7 @@ def select_receive_transfer_type(self): TransferStatusEnumModel.RECEIVE.value, ) else: - self._view_model.page_navigation.receive_rgb25_page( + self._view_model.page_navigation.receive_cfa_page( params=AssetDataModel( asset_type=self.asset_type, asset_id=self.asset_id_detail.toPlainText(), ), @@ -509,7 +507,7 @@ def select_send_transfer_type(self): TransferStatusEnumModel.SEND.value, ) else: - self._view_model.page_navigation.send_rgb25_page() + self._view_model.page_navigation.send_cfa_page() def setup_ui_connection(self): """Set up connections for UI elements.""" @@ -530,23 +528,23 @@ def setup_ui_connection(self): self.copy_button.clicked.connect( lambda: copy_text(self.asset_id_detail), ) - self._view_model.rgb25_view_model.txn_list_loaded.connect( + self._view_model.cfa_view_model.txn_list_loaded.connect( self.set_transaction_detail_frame, ) self.close_btn.clicked.connect( self.handle_page_navigation, ) - self._view_model.rgb25_view_model.is_loading.connect( + self._view_model.cfa_view_model.is_loading.connect( self.show_loading_screen, ) self.asset_refresh_button.clicked.connect( - self._view_model.rgb25_view_model.on_refresh_click, + self._view_model.cfa_view_model.on_refresh_click, ) def refresh_transaction(self): """Refresh the transaction of the assets""" self.render_timer.start() - self._view_model.rgb25_view_model.on_refresh_click() + self._view_model.cfa_view_model.on_refresh_click() def set_transaction_detail_frame(self, asset_id, asset_name, image_path, asset_type): """This method sets up the transaction detail frame in the UI. @@ -557,7 +555,7 @@ def set_transaction_detail_frame(self, asset_id, asset_name, image_path, asset_t self.asset_type = asset_type self.asset_name = asset_name self.handle_img_path(image_path=self.image_path) - asset_transactions: ListOnAndOffChainTransfersWithBalance = self._view_model.rgb25_view_model.txn_list + asset_transactions: ListOnAndOffChainTransfersWithBalance = self._view_model.cfa_view_model.txn_list self.asset_total_balance.setText( str(asset_transactions.asset_balance.future), ) @@ -591,7 +589,7 @@ def set_transaction_detail_frame(self, asset_id, asset_name, image_path, asset_t no_transaction_widget, 0, 0, 1, 1, ) return - if asset_type == AssetType.RGB20.value: + if asset_type == AssetType.NIA.value: self.rgb_asset_detail_widget.setMinimumSize(QSize(499, 730)) self.rgb_asset_detail_widget.setMaximumSize(QSize(499, 730)) self.scroll_area.setMaximumSize(QSize(335, 225)) @@ -638,7 +636,7 @@ def set_transaction_detail_frame(self, asset_id, asset_name, image_path, asset_t def handle_asset_frame_click(self, params: TransactionDetailPageModel): """Pass emit value to navigation page""" - self._view_model.page_navigation.rgb25_transaction_detail_page(params) + self._view_model.page_navigation.cfa_transaction_detail_page(params) def handle_show_hide(self, transaction_detail_frame): """It handled to hide and show transaction details frame""" @@ -681,38 +679,12 @@ def show_loading_screen(self, loading: bool): self.receive_rgb_asset.setDisabled(False) def handle_page_navigation(self): - """Handle the page navigation according the RGB20 or RGB25 page""" - if self.asset_type == AssetType.RGB20.value: + """Handle the page navigation according the NIA or CFA page""" + if self.asset_type == AssetType.NIA.value: self._view_model.page_navigation.fungibles_asset_page() else: self._view_model.page_navigation.collectibles_asset_page() - def is_path(self, file_path): - """Check the file path""" - if not isinstance(file_path, str): - return False - # Define a basic regex pattern for Unix-like file paths - pattern = r'^(\/[a-zA-Z0-9_.-]+)+\/?$' - # Check if the file_path matches the pattern - return bool(re.match(pattern, file_path)) - - def is_hex_string(self, bytes_hex): - """Check if the string is a valid hex string.""" - if len(bytes_hex) % 2 != 0: - return False - hex_pattern = re.compile(r'^[0-9a-fA-F]+$') - return bool(hex_pattern.match(bytes_hex)) - - def set_asset_image(self, image_hex): - """This method set the asset image according to the media path or image hex """ - if self.is_hex_string(image_hex): - pixmap = convert_hex_to_image(image_hex) - resized_image = resize_image(pixmap, 335, 335) - self.label_asset_name.setPixmap(resized_image) - else: - resized_image = resize_image(image_hex, 335, 335) - self.label_asset_name.setPixmap(resized_image) - def is_channel_open_for_asset(self): """Check if there is an open channel for the current asset.""" self.asset_id_detail.textChanged.connect(self.set_lightning_balance) @@ -772,35 +744,21 @@ def handle_fail_transfer(self, idx, tx_id): def _confirm_fail_transfer(self, idx): """Confirms the fail transfer action and closes the confirmation dialog.""" - self._view_model.rgb25_view_model.on_fail_transfer(idx) + self._view_model.cfa_view_model.on_fail_transfer(idx) def handle_img_path(self, image_path): - """ - Configures the asset detail widget and related components based on the provided image path. - Adjusts the layout and styles, and sets the asset image. - """ + """Configure the asset detail widget based on the provided image path.""" if image_path: - self.rgb_asset_detail_widget.setMinimumSize(QSize(466, 848)) - self.rgb_asset_detail_widget.setFixedWidth(499) self.lightning_balance_frame.setMinimumSize(QSize(159, 120)) - self.label_asset_name = QLabel(self.rgb_asset_detail_widget) - self.label_asset_name.setObjectName('label_asset_name') - self.label_asset_name.setMaximumSize(QSize(335, 335)) - self.asset_id_frame.setMinimumSize(QSize(335, 86)) - self.asset_id_frame.setMaximumSize(QSize(335, 86)) - self.label_asset_name.setStyleSheet( - "font: 14px \"Inter\";\n" - 'color: #B3B6C3;\n' - 'background: transparent;\n' - 'border: none;\n' - 'border-radius: 8px;\n' - 'font-weight: 400;\n' - '', - ) - self.asset_image_layout.addWidget( - self.label_asset_name, 0, Qt.AlignHCenter, + self.label_asset_name = handle_img_path( + self.rgb_asset_detail_widget, + image_path, + self.asset_image_layout, + self.asset_id_frame, + self.label_asset_name if hasattr( + self, 'label_asset_name', + ) else None, ) - self.set_asset_image(image_hex=image_path) self.transactions_label.setMinimumWidth(305) def set_on_chain_transaction_frame(self, transaction, asset_name, asset_type, asset_id, image_path): diff --git a/src/views/ui_rgb_asset_transaction_detail.py b/src/views/ui_rgb_asset_transaction_detail.py index f814f00c..95d15b79 100644 --- a/src/views/ui_rgb_asset_transaction_detail.py +++ b/src/views/ui_rgb_asset_transaction_detail.py @@ -442,18 +442,18 @@ def handle_close(self): """ Handle the close action for the transaction detail view. - This method emits a signal with the asset information and navigates to the RGB25 detail page. + This method emits a signal with the asset information and navigates to the CFA detail page. Attributes: self (object): The instance of the class containing the view model and navigation logic. """ - self._view_model.rgb25_view_model.asset_info.emit( + self._view_model.cfa_view_model.asset_info.emit( self.params.asset_id, self.params.asset_name, self.params.image_path, self.params.asset_type, ) - self._view_model.page_navigation.rgb25_detail_page( + self._view_model.page_navigation.cfa_detail_page( RgbAssetPageLoadModel(asset_type=self.params.asset_type), ) diff --git a/src/views/ui_send_ln_invoice.py b/src/views/ui_send_ln_invoice.py index f4230cde..45e025d1 100644 --- a/src/views/ui_send_ln_invoice.py +++ b/src/views/ui_send_ln_invoice.py @@ -648,7 +648,7 @@ def send_asset(self): def on_success_sent_navigation(self): """This method is used to navigate to collectibles or fungibles page when the originating page is create ln invoice""" - if self.asset_type == AssetType.RGB25.value: + if self.asset_type == AssetType.CFA.value: self._view_model.page_navigation.collectibles_asset_page() else: self._view_model.page_navigation.fungibles_asset_page() @@ -671,7 +671,7 @@ def update_loading_state(self, is_loading: bool): def on_click_close_button(self): """This method is used to navigate to fungibles or collectibles page based on asset type""" - if self.asset_type == AssetType.RGB25.value: + if self.asset_type == AssetType.CFA.value: self._view_model.page_navigation.collectibles_asset_page() else: self._view_model.page_navigation.fungibles_asset_page() diff --git a/src/views/ui_send_rgb_asset.py b/src/views/ui_send_rgb_asset.py index c7674764..d6babca4 100644 --- a/src/views/ui_send_rgb_asset.py +++ b/src/views/ui_send_rgb_asset.py @@ -54,7 +54,7 @@ def __init__(self, view_model): layout = QVBoxLayout() layout.addWidget(self.send_rgb_asset_page) self.setLayout(layout) - self.set_originating_page(self._view_model.rgb25_view_model.asset_type) + self.set_originating_page(self._view_model.cfa_view_model.asset_type) self.setup_ui_connection() self.handle_button_enabled() self.set_asset_balance() @@ -72,8 +72,8 @@ def setup_ui_connection(self): self.send_rgb_asset_page.close_button.clicked.connect( self.rgb_asset_page_navigation, ) - self._view_model.rgb25_view_model.message.connect( - self.show_rgb25_message, + self._view_model.cfa_view_model.message.connect( + self.show_cfa_message, ) self.send_rgb_asset_page.asset_address_value.textChanged.connect( self.validate_rgb_invoice, @@ -84,16 +84,16 @@ def setup_ui_connection(self): self.send_rgb_asset_page.asset_address_value.textChanged.connect( self.handle_button_enabled, ) - self._view_model.rgb25_view_model.is_loading.connect( + self._view_model.cfa_view_model.is_loading.connect( self.update_loading_state, ) self.send_rgb_asset_page.refresh_button.clicked.connect( self.refresh_asset, ) - self._view_model.rgb25_view_model.txn_list_loaded.connect( + self._view_model.cfa_view_model.txn_list_loaded.connect( self.set_asset_balance, ) - self._view_model.rgb25_view_model.txn_list_loaded.connect( + self._view_model.cfa_view_model.txn_list_loaded.connect( self.handle_spendable_balance_validation, ) self._view_model.estimate_fee_view_model.loading_status.connect( @@ -106,25 +106,25 @@ def setup_ui_connection(self): def refresh_asset(self): """This method handle the refresh asset on send asset page""" self.loading_performer = 'REFRESH_BUTTON' - view_model = self._view_model.rgb25_view_model + view_model = self._view_model.cfa_view_model view_model.on_refresh_click() self.asset_id = view_model.asset_id self.asset_name = view_model.asset_name self.image_path = view_model.image_path self.asset_type = view_model.asset_type - self._view_model.rgb25_view_model.get_rgb25_asset_detail( + self._view_model.cfa_view_model.get_cfa_asset_detail( asset_id=self.asset_id, asset_name=self.asset_name, image_path=self.image_path, asset_type=self.asset_type, ) def set_originating_page(self, asset_type): """This method sets the originating page for when closing send asset""" - if asset_type == 'RGB20': - self.asset_type = 'RGB20' + if asset_type == 'NIA': + self.asset_type = 'NIA' def rgb_asset_page_navigation(self): """Navigate to the collectibles asset page.""" self.sidebar = self._view_model.page_navigation.sidebar() - if self.asset_type == 'RGB20': + if self.asset_type == 'NIA': self.sidebar.my_fungibles.setChecked(True) self._view_model.page_navigation.fungibles_asset_page() else: @@ -146,7 +146,7 @@ def send_rgb_asset_button(self): DecodeRgbInvoiceRequestModel(invoice=provided_invoice), ) try: - self._view_model.rgb25_view_model.on_send_click( + self._view_model.cfa_view_model.on_send_click( amount, decoded_rgb_invoice.recipient_id, decoded_rgb_invoice.transport_endpoints, fee_rate, default_min_confirmation.min_confirmation, ) # Success toast or indicator can be added here if needed @@ -161,7 +161,7 @@ def send_rgb_asset_button(self): description=ERROR_UNEXPECTED.format(str(e.message)), ) - def show_rgb25_message(self, msg_type: ToastPreset, message: str): + def show_cfa_message(self, msg_type: ToastPreset, message: str): """Handle show message""" if msg_type == ToastPreset.ERROR: ToastManager.error(message) @@ -254,7 +254,7 @@ def handle_show_message(self, msg_type: ToastPreset, message: str): def set_asset_balance(self): """Set the spendable and total balance of the asset""" - view_model = self._view_model.rgb25_view_model + view_model = self._view_model.cfa_view_model asset_transactions: ListTransferAssetWithBalanceResponseModel = view_model.txn_list self.asset_spendable_balance = asset_transactions.asset_balance.spendable self.send_rgb_asset_page.asset_balance_label_total.setText( diff --git a/src/views/ui_settings.py b/src/views/ui_settings.py index cc1cac9a..c17c7058 100644 --- a/src/views/ui_settings.py +++ b/src/views/ui_settings.py @@ -44,13 +44,16 @@ from src.utils.constant import BITCOIND_RPC_HOST_MAINNET from src.utils.constant import BITCOIND_RPC_HOST_REGTEST from src.utils.constant import BITCOIND_RPC_HOST_TESTNET +from src.utils.constant import BITCOIND_RPC_HOST_TESTNET4 from src.utils.constant import BITCOIND_RPC_PORT_MAINNET from src.utils.constant import BITCOIND_RPC_PORT_REGTEST from src.utils.constant import BITCOIND_RPC_PORT_TESTNET +from src.utils.constant import BITCOIND_RPC_PORT_TESTNET4 from src.utils.constant import FEE_RATE from src.utils.constant import INDEXER_URL_MAINNET from src.utils.constant import INDEXER_URL_REGTEST from src.utils.constant import INDEXER_URL_TESTNET +from src.utils.constant import INDEXER_URL_TESTNET4 from src.utils.constant import IRIS_WALLET_TRANSLATIONS_CONTEXT from src.utils.constant import LN_INVOICE_EXPIRY_TIME from src.utils.constant import LN_INVOICE_EXPIRY_TIME_UNIT @@ -59,6 +62,7 @@ from src.utils.constant import PROXY_ENDPOINT_MAINNET from src.utils.constant import PROXY_ENDPOINT_REGTEST from src.utils.constant import PROXY_ENDPOINT_TESTNET +from src.utils.constant import PROXY_ENDPOINT_TESTNET4 from src.utils.constant import WALLET_PASSWORD_KEY from src.utils.helpers import load_stylesheet from src.utils.info_message import INFO_VALIDATION_OF_NODE_PASSWORD_AND_KEYRING_ACCESS @@ -68,6 +72,9 @@ from src.views.components.header_frame import HeaderFrame from src.views.components.keyring_error_dialog import KeyringErrorDialog from src.views.components.loading_screen import LoadingTranslucentScreen +from src.views.components.settings_helpers import check_keyring_state_for_password +from src.views.components.settings_helpers import set_endpoint_based_on_network +from src.views.components.settings_helpers import set_frame_content from src.views.components.toast import ToastManager from src.views.components.toggle_switch import ToggleSwitch from src.views.ui_restore_mnemonic import RestoreMnemonicWidget @@ -91,7 +98,7 @@ def __init__(self, view_model): self.announce_address = ANNOUNCE_ADDRESS self.announce_alias = ANNOUNCE_ALIAS self.min_confirmation = MIN_CONFIRMATION - self._set_endpoint_based_on_network() + self.indexer_url, self.proxy_endpoint, self.bitcoind_host, self.bitcoind_port = set_endpoint_based_on_network() self.current_network = None self.grid_layout = QGridLayout(self) self.grid_layout.setSpacing(0) @@ -661,7 +668,7 @@ def _set_expiry_time(self): def _set_indexer_url(self): """Set the default indexer url based on user input. """ - password = self._check_keyring_state() + password = check_keyring_state_for_password(self, self._view_model) if password: self._view_model.setting_view_model.check_indexer_url_endpoint( self.set_indexer_url_frame.input_value.text(), password, @@ -669,7 +676,7 @@ def _set_indexer_url(self): def _set_proxy_endpoint(self): """Set the default proxy endpoint based on user input.""" - password = self._check_keyring_state() + password = check_keyring_state_for_password(self, self._view_model) if password: self._view_model.setting_view_model.check_proxy_endpoint( self.set_proxy_endpoint_frame.input_value.text(), password, @@ -677,7 +684,7 @@ def _set_proxy_endpoint(self): def _set_bitcoind_host(self): """ Set the default bitcoind host based on user input.""" - password = self._check_keyring_state() + password = check_keyring_state_for_password(self, self._view_model) if password: self._view_model.setting_view_model.set_bitcoind_host( self.set_bitcoind_rpc_host_frame.input_value.text(), password, @@ -685,7 +692,7 @@ def _set_bitcoind_host(self): def _set_bitcoind_port(self): """Set the default bitcoind port based on user input.""" - password = self._check_keyring_state() + password = check_keyring_state_for_password(self, self._view_model) if password: self._view_model.setting_view_model.set_bitcoind_port( int(self.set_bitcoind_rpc_port_frame.input_value.text()), password, @@ -693,7 +700,7 @@ def _set_bitcoind_port(self): def _set_announce_address(self): """Set the default announce address based on user input.""" - password = self._check_keyring_state() + password = check_keyring_state_for_password(self, self._view_model) if password: self._view_model.setting_view_model.set_announce_address( self.set_announce_address_frame.input_value.text(), password, @@ -701,7 +708,7 @@ def _set_announce_address(self): def _set_announce_alias(self): """Set the default announce alias based on user input.""" - password = self._check_keyring_state() + password = check_keyring_state_for_password(self, self._view_model) if password: self._view_model.setting_view_model.set_announce_alias( self.set_announce_alias_frame.input_value.text(), password, @@ -834,112 +841,21 @@ def _update_loading_state(self, is_loading: bool): else: frame.save_button.stop_loading() - def _check_keyring_state(self): - """Checks the keyring status and retrieves the wallet password, either - from secure storage if the keyring is disabled or via a user prompt - through a mnemonic dialog if enabled.""" - keyring_status = SettingRepository.get_keyring_status() - if keyring_status is False: - network: NetworkEnumModel = SettingRepository.get_wallet_network() - password: str = get_value(WALLET_PASSWORD_KEY, network.value) - return password - if keyring_status is True: - mnemonic_dialog = RestoreMnemonicWidget( - parent=self, view_model=self._view_model, origin_page='setting_card', mnemonic_visibility=False, - ) - mnemonic_dialog.mnemonic_detail_text_label.setText( - QCoreApplication.translate( - IRIS_WALLET_TRANSLATIONS_CONTEXT, 'lock_unlock_password_required', None, - ), - ) - mnemonic_dialog.mnemonic_detail_text_label.setFixedHeight(40) - result = mnemonic_dialog.exec() - if result == QDialog.Accepted: - password = mnemonic_dialog.password_input.text() - return password - return None - - def _set_endpoint_based_on_network(self): - """Sets various endpoints and configuration parameters - based on the currently selected wallet network.""" - network_config_map = { - NetworkEnumModel.MAINNET: (INDEXER_URL_MAINNET, PROXY_ENDPOINT_MAINNET, BITCOIND_RPC_HOST_MAINNET, BITCOIND_RPC_PORT_MAINNET), - NetworkEnumModel.TESTNET: (INDEXER_URL_TESTNET, PROXY_ENDPOINT_TESTNET, BITCOIND_RPC_HOST_TESTNET, BITCOIND_RPC_PORT_TESTNET), - NetworkEnumModel.REGTEST: (INDEXER_URL_REGTEST, PROXY_ENDPOINT_REGTEST, BITCOIND_RPC_HOST_REGTEST, BITCOIND_RPC_PORT_REGTEST), - } - stored_network: NetworkEnumModel = SettingRepository.get_wallet_network() - config = network_config_map.get(stored_network) - if config: - self.indexer_url, self.proxy_endpoint, self.bitcoind_host, self.bitcoind_port = config - else: - raise ValueError(f"Unsupported network type: {stored_network}") - - def _set_frame_content(self, frame, input_value, validator=None, time_unit_combobox=None, suggestion_desc=None): - """ - Sets the content for a given frame, configuring the input value and optionally hiding/showing other widgets. - """ - if isinstance(input_value, float) and input_value.is_integer(): - input_value = int(input_value) - - frame.input_value.setText(str(input_value)) - frame.input_value.setPlaceholderText(str(input_value)) - frame.input_value.setValidator(validator) - - if not suggestion_desc: - frame.suggestion_desc.hide() - - if time_unit_combobox: - index = time_unit_combobox.findText( - self.expiry_time_unit, Qt.MatchFixedString, - ) - if index != -1: - time_unit_combobox.setCurrentIndex(index) - else: - frame.time_unit_combobox.hide() - - frame.input_value.textChanged.connect( - lambda: self._update_save_button(frame, input_value), - ) - - if time_unit_combobox: - frame.time_unit_combobox.currentTextChanged.connect( - lambda: self._update_save_button( - frame, input_value, time_unit_combobox, - ), - ) - - # Initial call to set the correct button state - self._update_save_button(frame, input_value, time_unit_combobox) - - def _update_save_button(self, frame, input_value, time_unit_combobox=None): - """ - Updates the state of the save button based on input value and time unit changes. - """ - current_text = frame.input_value.text().strip() - current_unit = frame.time_unit_combobox.currentText() if time_unit_combobox else '' - - time_unit_changed = current_unit != self.expiry_time_unit - - if current_text and (current_text != str(input_value) or (time_unit_combobox and time_unit_changed)): - frame.save_button.setDisabled(False) - else: - frame.save_button.setDisabled(True) - def handle_fee_rate_frame(self): """Handle the frame for setting the fee rate.""" - self._set_frame_content( + set_frame_content( self.set_fee_rate_frame, self.fee_rate, - QIntValidator(), + validator=QIntValidator(), suggestion_desc=self.set_fee_rate_frame.suggestion_desc, ) def handle_expiry_time_frame(self): """Handle the frame for setting the expiry time and unit.""" - self._set_frame_content( + set_frame_content( self.set_expiry_time_frame, self.expiry_time, - QIntValidator(), + validator=QIntValidator(), suggestion_desc=self.set_expiry_time_frame.suggestion_desc, time_unit_combobox=self.set_expiry_time_frame.time_unit_combobox, @@ -950,50 +866,50 @@ def handle_expiry_time_frame(self): def handle_indexer_url_frame(self): """Handle the frame for setting the indexer url.""" - self._set_frame_content( + set_frame_content( self.set_indexer_url_frame, self.indexer_url, ) def handle_proxy_endpoint_frame(self): """Handle the frame for setting the proxy endpoint.""" - self._set_frame_content( + set_frame_content( self.set_proxy_endpoint_frame, self.proxy_endpoint, ) def handle_bitcoind_host_frame(self): """Handle the frame for setting the bitcoind host.""" - self._set_frame_content( + set_frame_content( self.set_bitcoind_rpc_host_frame, self.bitcoind_host, ) def handle_bitcoind_port_frame(self): """Handle the frame for setting the bitcoind port.""" - self._set_frame_content( + set_frame_content( self.set_bitcoind_rpc_port_frame, self.bitcoind_port, - QIntValidator(), + validator=QIntValidator(), ) def handle_announce_address_frame(self): """Handle the frame for setting the announce address.""" - self._set_frame_content( + set_frame_content( self.set_announce_address_frame, self.announce_address, ) def handle_announce_alias_frame(self): """Handle the frame for setting the announce alias.""" - self._set_frame_content( + set_frame_content( self.set_announce_alias_frame, self.announce_alias, ) def handle_minimum_confirmation_frame(self): """Handle the frame for setting the minimum confirmation.""" - self._set_frame_content( + set_frame_content( self.set_minimum_confirmation_frame, self.min_confirmation, QIntValidator(), diff --git a/src/views/ui_sidebar.py b/src/views/ui_sidebar.py index 060a931f..56eeaa0c 100644 --- a/src/views/ui_sidebar.py +++ b/src/views/ui_sidebar.py @@ -200,7 +200,7 @@ def setup_ui_connections(self): ) self.help.clicked.connect(self._view_model.page_navigation.help_page) self.receive_asset_button.clicked.connect( - lambda: self._view_model.page_navigation.receive_rgb25_page( + lambda: self._view_model.page_navigation.receive_cfa_page( params=AssetDataModel( asset_type=self.get_checked_button_translation_key(), ), diff --git a/src/views/ui_view_unspent_list.py b/src/views/ui_view_unspent_list.py index 2f5dc138..a89f2d78 100644 --- a/src/views/ui_view_unspent_list.py +++ b/src/views/ui_view_unspent_list.py @@ -33,7 +33,6 @@ from src.viewmodels.main_view_model import MainViewModel from src.views.components.header_frame import HeaderFrame from src.views.components.loading_screen import LoadingTranslucentScreen -from src.views.components.toast import ToastManager class ViewUnspentList(QWidget): @@ -325,4 +324,5 @@ def get_image_path(self, _list): NetworkEnumModel.MAINNET.value: ':/assets/bitcoin.png', NetworkEnumModel.REGTEST.value: ':/assets/regtest_bitcoin.png', NetworkEnumModel.TESTNET.value: ':/assets/testnet_bitcoin.png', + NetworkEnumModel.TESTNET4.value: ':/assets/testnet_bitcoin.png', }.get(self.network.value) diff --git a/src/views/ui_wallet_or_transfer_selection.py b/src/views/ui_wallet_or_transfer_selection.py index 4cc44ac3..b3eb5f67 100644 --- a/src/views/ui_wallet_or_transfer_selection.py +++ b/src/views/ui_wallet_or_transfer_selection.py @@ -346,13 +346,13 @@ def handle_frame_click(self, _id): elif _id == TransferType.ON_CHAIN.value: # Navigate to the appropriate page based on the transfer type if transfer_type == TransferStatusEnumModel.RECEIVE.value: - self._view_model.page_navigation.receive_rgb25_page( + self._view_model.page_navigation.receive_cfa_page( params=AssetDataModel( asset_type=self.asset_type, asset_id=self._params.asset_id, ), ) elif transfer_type == TransferStatusEnumModel.SEND.value: - self._view_model.page_navigation.send_rgb25_page() + self._view_model.page_navigation.send_cfa_page() elif transfer_type == TransferStatusEnumModel.SEND_BTC.value: self._view_model.page_navigation.send_bitcoin_page() elif transfer_type == TransferStatusEnumModel.RECEIVE_BTC.value: @@ -415,7 +415,7 @@ def close_button_navigation(self): self._params.back_page_navigation() if self._params.rgb_asset_page_load_model is not None: - self._view_model.rgb25_view_model.asset_info.emit( + self._view_model.cfa_view_model.asset_info.emit( self._params.rgb_asset_page_load_model.asset_id, self._params.rgb_asset_page_load_model.asset_name, self._params.rgb_asset_page_load_model.image_path, diff --git a/unit_tests/service_test_resources/mocked_fun_return_values/asset_detail_page_service.py b/unit_tests/service_test_resources/mocked_fun_return_values/asset_detail_page_service.py index 9ee5593d..cc203d2a 100644 --- a/unit_tests/service_test_resources/mocked_fun_return_values/asset_detail_page_service.py +++ b/unit_tests/service_test_resources/mocked_fun_return_values/asset_detail_page_service.py @@ -3,8 +3,10 @@ """ from __future__ import annotations +from src.model.enums.enums_model import AssignmentEnumModel from src.model.enums.enums_model import TransferStatusEnumModel from src.model.rgb_model import AssetBalanceResponseModel +from src.model.rgb_model import AssignmentModel from src.model.rgb_model import ListTransferAssetResponseModel from src.model.rgb_model import ListTransferAssetWithBalanceResponseModel from src.model.rgb_model import TransportEndpoint @@ -15,7 +17,14 @@ created_at=1717565849, updated_at=1717565849, status='Settled', - amount=1600, + assignments=[ + AssignmentModel( + type=AssignmentEnumModel.FUNGIBLE, value=1600, + ), + ], + requested_assignment=AssignmentModel( + type=AssignmentEnumModel.FUNGIBLE, value=1600, + ), kind='Issuance', ) @@ -24,7 +33,14 @@ created_at=1717566312, updated_at=1717567082, status='Settled', - amount=1000, + requested_assignment=AssignmentModel( + type=AssignmentEnumModel.FUNGIBLE, value=1000, + ), + assignments=[ + AssignmentModel( + type=AssignmentEnumModel.FUNGIBLE, value=1000, + ), + ], kind='Send', txid='5872b8b5333054e1e3768d897d9d0ccceb0e5a9388f2f83649241e8d2125a6ae', recipient_id='utxob:2okFKi2-8Ex84DQNt-jzCHrU4HA-vozR9aDut-VEdc5yBUX-Ktfqhk8', @@ -43,7 +59,10 @@ created_at=1717566191, updated_at=1717567096, status='Settled', - amount=42, + requested_assignment=AssignmentModel( + type=AssignmentEnumModel.FUNGIBLE, value=42, + ), + assignments=[AssignmentModel(type=AssignmentEnumModel.FUNGIBLE, value=42)], kind='ReceiveBlind', txid='5872b8b5333054e1e3768d897d9d0ccceb0e5a9388f2f83649241e8d2125a6ae', recipient_id='utxob:2okFKi2-8Ex84DQNt-jzCHrU4HA-vozR9aDut-VEdc5yBUX-Ktfqhk8', @@ -63,7 +82,10 @@ created_at=1717566191, updated_at=1717567096, status='Settled', - amount=42, + requested_assignment=AssignmentModel( + type=AssignmentEnumModel.FUNGIBLE, value=42, + ), + assignments=[AssignmentModel(type=AssignmentEnumModel.FUNGIBLE, value=42)], kind='ReceiveWitness', txid='5872b8b5333054e1e3768d897d9d0ccceb0e5a9388f2f83649241e8d2125a6ae', recipient_id='utxob:2okFKi2-8Ex84DQNt-jzCHrU4HA-vozR9aDut-VEdc5yBUX-Ktfqhk8', @@ -83,7 +105,10 @@ created_at=1717566191, updated_at=1717567096, status='Settled', - amount=42, + requested_assignment=AssignmentModel( + type=AssignmentEnumModel.FUNGIBLE, value=42, + ), + assignments=[AssignmentModel(type=AssignmentEnumModel.FUNGIBLE, value=42)], kind='Invalid', txid='5872b8b5333054e1e3768d897d9d0ccceb0e5a9388f2f83649241e8d2125a6ae', recipient_id='utxob:2okFKi2-8Ex84DQNt-jzCHrU4HA-vozR9aDut-VEdc5yBUX-Ktfqhk8', diff --git a/unit_tests/service_test_resources/mocked_fun_return_values/faucet_service.py b/unit_tests/service_test_resources/mocked_fun_return_values/faucet_service.py index a7cb95dd..21b8831f 100644 --- a/unit_tests/service_test_resources/mocked_fun_return_values/faucet_service.py +++ b/unit_tests/service_test_resources/mocked_fun_return_values/faucet_service.py @@ -67,7 +67,8 @@ 'eventual_close_fees_sat': 1, 'pending_outbound_payments_sat': 1, 'num_peers': 1, - 'onchain_pubkey': 'tpubDDqiQYzNMGsKVVmWYG4FCPPcbd5S4uW9u7a6zUFkgbh16VmFTvsaNyo37mAgkCku6jStBcU8VaLxG2SE7ab2sEwwagjfbS8U9H82BadjKR1', + 'account_xpub_vanilla': 'tpubDDqiQYzNMGsKVVmWYG4FCPPcbd5S4uW9u7a6zUFkgbh16VmFTvsaNyo37mAgkCku6jStBcU8VaLxG2SE7ab2sEwwagjfbS8U9H82BadjKR1', + 'account_xpub_colored': 'tpubDDqiQYzNMGsKVVmWYG4FCPPcbd5S4uW9u7a6zUFkgbh16VmFTvsaNyo37mAgkCku6jStBcU8VaLxG2SE7ab2sEwwagjfbS8U9H82BadjKR1', 'max_media_upload_size_mb': 5, 'rgb_htlc_min_msat': 1, 'rgb_channel_capacity_min_sat': 1, diff --git a/unit_tests/service_test_resources/mocked_fun_return_values/issue_asset_service.py b/unit_tests/service_test_resources/mocked_fun_return_values/issue_asset_service.py index cbe3f6ac..cb03e24f 100644 --- a/unit_tests/service_test_resources/mocked_fun_return_values/issue_asset_service.py +++ b/unit_tests/service_test_resources/mocked_fun_return_values/issue_asset_service.py @@ -38,7 +38,6 @@ example_data_of_issue_asset_api = { 'asset_id': 'rgb:2dkSTbr-jFhznbPmo-TQafzswCN-av4gTsJjX-ttx6CNou5-M98k8Zd', - 'asset_iface': 'RGB20', 'name': 'Collectible', 'details': 'asset details', 'precision': 0, diff --git a/unit_tests/service_test_resources/mocked_fun_return_values/main_asset_service.py b/unit_tests/service_test_resources/mocked_fun_return_values/main_asset_service.py index c48644f2..6f823fcb 100644 --- a/unit_tests/service_test_resources/mocked_fun_return_values/main_asset_service.py +++ b/unit_tests/service_test_resources/mocked_fun_return_values/main_asset_service.py @@ -13,7 +13,6 @@ mock_nia_asset = AssetModel( asset_id='rgb:2dkSTbr-jFhznbPmo-TQafzswCN-av4gTsJjX-ttx6CNou5-M98k8Zd', - asset_iface='RGB20', ticker='USDT', name='Tether', details='asset details', @@ -30,7 +29,6 @@ mock_uda_asset = AssetModel( asset_id='rgb:2dkSTbr-jFhznbPmo-TQafzswCN-av4gTsJjX-ttx6CNou5-M98k8Zd', - asset_iface='RGB20', ticker='UNI', name='Unique', details='asset details', @@ -74,7 +72,6 @@ mock_cfa_asset = AssetModel( asset_id='rgb:2dkSTbr-jFhznbPmo-TQafzswCN-av4gTsJjX-ttx6CNou5-M98k8Zd', - asset_iface='RGB20', name='Collectible', details='asset details', precision=0, @@ -94,7 +91,6 @@ mock_cfa_asset_when_wallet_type_connect = AssetModel( asset_id='rgb:2dkSTbr-jFhznbPmo-TQafzswCN-av4gTsJjX-ttx6CNou5-M98k8Zd', - asset_iface='RGB20', name='Collectible', details='asset details', precision=0, @@ -133,7 +129,8 @@ eventual_close_fees_sat=892, pending_outbound_payments_sat=7852, num_peers=0, - onchain_pubkey='02270dadcd6e7ba0ef707dac72acccae1a3607453a8dd2aef36ff3be4e0d31f043', + account_xpub_vanilla='02270dadcd6e7ba0ef707dac72acccae1a3607453a8dd2aef36ff3be4e0d31f043', + account_xpub_colored='02270dadcd6e7ba0ef707dac72acccae1a3607453a8dd2aef36ff3be4e0d31f043', max_media_upload_size_mb=5, rgb_htlc_min_msat=1, rgb_channel_capacity_min_sat=1, @@ -150,7 +147,6 @@ mock_nia_asset_exhausted_asset = AssetModel( asset_id='rgb:2dkSTbr-jFhznbPmo-TQafzswCN-av4gTsJjX-ttx6CNou5-M98k333', - asset_iface='RGB20', ticker='TTK', name='super man', details='asset details', @@ -167,7 +163,6 @@ mock_uda_asset_exhausted_asset = AssetModel( asset_id='rgb:2dkSTbr-jFhznbPmo-TQafzswCN-av4gTsJjX-ttx6CNou5-M98k8Zd', - asset_iface='RGB20', ticker='UNI', name='Unique', details='asset details', @@ -211,7 +206,6 @@ mock_cfa_asset_exhausted_asset = AssetModel( asset_id='rgb:2dkSTbr-jFhznbPmo-TQafzswCN-av4gTsJjX-ttx6CNou5-M98k8Zd', - asset_iface='RGB20', name='Collectible', details='asset details', precision=0, diff --git a/unit_tests/tests/conftest.py b/unit_tests/tests/conftest.py index 9a19ada5..ea17a45f 100644 --- a/unit_tests/tests/conftest.py +++ b/unit_tests/tests/conftest.py @@ -15,6 +15,11 @@ """ from __future__ import annotations +import gc +import os +import tempfile + +import keyring import pytest from PySide6.QtCore import QThread from PySide6.QtWidgets import QApplication @@ -34,10 +39,7 @@ def qt_app(): def mock_qthread_start(mocker): """ Automatically mock QThread.start() to prevent actual thread creation. - This prevents 'QThread: Destroyed while thread is still running' errors - that cause pytest-xdist to fail with 'ValueError: list.remove(x): x not in list'. - Tests that need actual threading should call thread.run() directly instead. """ @@ -50,15 +52,59 @@ def mock_start_wrapper(self): @pytest.fixture(autouse=True) -def mock_toast_manager(mocker, request): +def mock_qthreadpool(mocker): + """ + Automatically mock QThreadPool to prevent actual thread creation. + This prevents Qt code execution inside mocks during parallel tests. + """ + mock_pool = mocker.Mock() + # Mock globalInstance to return our mock pool + mocker.patch( + 'PySide6.QtCore.QThreadPool.globalInstance', + return_value=mock_pool, + ) + # Mock constructor to return our mock pool + mocker.patch('PySide6.QtCore.QThreadPool', return_value=mock_pool) + # Ensure start does nothing + mock_pool.start.side_effect = lambda runnable: None + + +@pytest.fixture(autouse=True) +def mock_timer(mocker, request): + """ + Prevent HeaderFrameViewModel from starting infinite thread loops via QTimer. + Skip for header_frame_view_model_test.py so it can test the actual logic. + """ + if 'header_frame_view_model_test.py' in str(request.fspath): + return + + mocker.patch( + 'src.viewmodels.header_frame_view_model.HeaderFrameViewModel.start_network_check', + ) + + +@pytest.fixture(autouse=True) +def gc_collect(): + """Force garbage collection after each test to prevent QObject accumulation.""" + yield + gc.collect() + + +@pytest.fixture(autouse=True) +def mock_network_calls(mocker): """ - Automatically mock ToastManager methods to prevent 'Main window not set' errors. + Mock socket.create_connection to prevent real network calls during tests. + """ + mock_socket = mocker.MagicMock() + mocker.patch('socket.create_connection', return_value=mock_socket) - ToastManager requires a main window to be set before creating toasts, - which is not available in unit tests. This fixture mocks all toast methods - to prevent the ValueError from being raised during tests. - Skip mocking for toast_test.py since those tests specifically test ToastManager. +@pytest.fixture(autouse=True) +def mock_toast_manager(mocker, request): + """ + Mock ToastManager methods to isolate tests from UI dependencies. + ToastManager requires a main window, which is unavailable in unit tests. + Skipped for toast_test.py to allow direct ToastManager testing. """ # Skip mocking for toast_test.py which specifically tests ToastManager if 'toast_test.py' in str(request.fspath): @@ -68,3 +114,65 @@ def mock_toast_manager(mocker, request): mocker.patch('src.views.components.toast.ToastManager.success') mocker.patch('src.views.components.toast.ToastManager.info') mocker.patch('src.views.components.toast.ToastManager.warning') + +# ---------------- Global Safety Fixtures ----------------- + + +@pytest.fixture(scope='session', autouse=True) +def _isolate_user_dirs(): + """Redirect user data/config/cache dirs to a temporary location for the entire test session.""" + with tempfile.TemporaryDirectory(prefix='iris_wallet_tests_') as td: + os.environ.setdefault('HOME', td) + os.environ.setdefault( + 'XDG_DATA_HOME', os.path.join(td, '.local', 'share'), + ) + os.environ.setdefault('XDG_CONFIG_HOME', os.path.join(td, '.config')) + os.environ.setdefault('XDG_CACHE_HOME', os.path.join(td, '.cache')) + os.environ.setdefault( + 'IRIS_WALLET_DATA_DIR', + os.path.join(td, 'iris-wallet-vault'), + ) + yield + + +@pytest.fixture(autouse=True) +def _mock_keyring(monkeypatch): + """Ensure keyring operations are in-memory and never hit the system keychain.""" + + _store: dict[tuple[str, str], str] = {} + + def _get_password(service_name: str, username: str) -> str | None: + return _store.get((service_name, username)) + + def _set_password(service_name: str, username: str, password: str) -> None: + _store[(service_name, username)] = password + + def _delete_password(service_name: str, username: str) -> None: + _store.pop((service_name, username), None) + + monkeypatch.setattr(keyring, 'get_password', _get_password, raising=False) + monkeypatch.setattr(keyring, 'set_password', _set_password, raising=False) + monkeypatch.setattr( + keyring, 'delete_password', + _delete_password, raising=False, + ) + + if hasattr(keyring, 'set_keyring'): + class _InMemoryBackend: # pragma: no cover + priority = 1 + + def get_password(self, service: str, username: str) -> str | None: + """Get password from in-memory store.""" + return _get_password(service, username) + + def set_password(self, service: str, username: str, password: str) -> None: + """Set password in in-memory store.""" + return _set_password(service, username, password) + + def delete_password(self, service: str, username: str) -> None: + """Delete password from in-memory store.""" + return _delete_password(service, username) + try: + keyring.set_keyring(_InMemoryBackend()) + except Exception: + pass diff --git a/unit_tests/tests/service_tests/asset_detail_page_test/get_asset_transactions_test.py b/unit_tests/tests/service_tests/asset_detail_page_test/get_asset_transactions_test.py index 3325f049..ae572a92 100644 --- a/unit_tests/tests/service_tests/asset_detail_page_test/get_asset_transactions_test.py +++ b/unit_tests/tests/service_tests/asset_detail_page_test/get_asset_transactions_test.py @@ -6,12 +6,9 @@ from src.data.service.asset_detail_page_services import AssetDetailPageService from src.model.payments_model import ListPaymentResponseModel -from src.model.rgb_model import AssetBalanceResponseModel from src.model.rgb_model import AssetIdModel from src.model.rgb_model import ListOnAndOffChainTransfersWithBalance -from src.model.rgb_model import ListTransferAssetWithBalanceResponseModel from src.model.rgb_model import ListTransfersRequestModel -from src.model.rgb_model import TransferAsset from src.utils.custom_exception import CommonException from src.utils.custom_exception import ServiceOperationException from unit_tests.repository_fixture.rgb_repository_mock import mock_get_asset_balance diff --git a/unit_tests/tests/service_tests/asset_detail_page_test/get_single_asset_transaction_test.py b/unit_tests/tests/service_tests/asset_detail_page_test/get_single_asset_transaction_test.py index 4fedfdd1..c42b5c0f 100644 --- a/unit_tests/tests/service_tests/asset_detail_page_test/get_single_asset_transaction_test.py +++ b/unit_tests/tests/service_tests/asset_detail_page_test/get_single_asset_transaction_test.py @@ -47,7 +47,7 @@ def test_get_single_asset_transaction_by_txid(mocked_get_asset_transaction_servi ) assert result == mocked_data_when_transaction_type_send assert result.idx == mocked_data_when_transaction_type_send.idx - assert result.amount == mocked_data_when_transaction_type_send.amount + assert result.assignments[0].value == mocked_data_when_transaction_type_send.assignments[0].value assert result.txid == mocked_data_when_transaction_type_send.txid assert ( result.created_at_date == mocked_data_when_transaction_type_send.created_at_date @@ -104,7 +104,7 @@ def test_get_single_asset_transaction_by_idx(mocked_get_asset_transaction_servic ) assert result == mocked_data_when_transaction_type_send assert result.idx == mocked_data_when_transaction_type_send.idx - assert result.amount == mocked_data_when_transaction_type_send.amount + assert result.assignments[0].value == mocked_data_when_transaction_type_send.assignments[0].value assert result.txid == mocked_data_when_transaction_type_send.txid assert ( result.created_at_date == mocked_data_when_transaction_type_send.created_at_date diff --git a/unit_tests/tests/service_tests/backup_test.py b/unit_tests/tests/service_tests/backup_test.py index df2f61ba..186148d3 100644 --- a/unit_tests/tests/service_tests/backup_test.py +++ b/unit_tests/tests/service_tests/backup_test.py @@ -52,24 +52,33 @@ def teardown_directory_after_test(): @patch('src.data.service.common_operation_service.CommonOperationService.get_hashed_mnemonic') -@patch('src.utils.local_store.local_store.get_path') +@patch('src.data.service.backup_service.app_paths') @patch('src.data.service.backup_service.BackupService.backup_file_exists') @patch('src.data.repository.common_operations_repository.CommonOperationRepository.backup') +@patch('src.data.service.backup_service.write_ln_node_commit_id_file') @patch('src.data.service.backup_service.GoogleDriveManager') -def test_backup(mock_google_drive_manager, mock_backup, mock_backup_file_exits, mock_get_path, mock_get_hashed_mnemonic, setup_directory): +def test_backup(mock_google_drive_manager, mock_write_commit_file, mock_backup, mock_backup_file_exits, mock_app_paths, mock_get_hashed_mnemonic, setup_directory): """Case 1 : Test backup service""" - test_dir, _ = setup_directory + test_dir, backup_dir = setup_directory # Setup mocks mock_get_hashed_mnemonic.return_value = 'e23ddff3cc' - mock_get_path.return_value = test_dir + mock_app_paths.backup_folder_path = backup_dir + mock_app_paths.iriswallet_temp_folder_path = test_dir mock_backup_instance = MagicMock() mock_backup.return_value = None mock_backup_file_exits.return_value = True mock_backup_instance.return_value = None mock_google_drive_manager.return_value = mock_backup_instance - mock_backup_instance.upload_to_drive.return_value = True + # Two uploads: backup file and commit id file + mock_backup_instance.upload_to_drive.side_effect = [True, True] + # commit id file path and name + mock_write_commit_file.return_value = ( + os.path.join( + backup_dir, 'e23ddff3cc.commit', + ), 'e23ddff3cc.commit', + ) result = BackupService.backup(mock_valid_mnemonic, mock_password) @@ -80,40 +89,53 @@ def test_backup(mock_google_drive_manager, mock_backup, mock_backup_file_exits, @patch('src.data.service.common_operation_service.CommonOperationService.get_hashed_mnemonic') -@patch('src.utils.local_store.local_store.get_path') +@patch('src.data.service.backup_service.app_paths') @patch('src.data.service.backup_service.BackupService.backup_file_exists') @patch('src.data.repository.common_operations_repository.CommonOperationRepository.backup') +@patch('src.data.service.backup_service.write_ln_node_commit_id_file') @patch('src.data.service.backup_service.GoogleDriveManager') -def test_backup_when_backup_file_not_exits(mock_google_drive_manager, mock_backup, mock_backup_file_exits, mock_get_path, mock_get_hashed_mnemonic, setup_directory): +def test_backup_when_backup_file_not_exits( + mock_google_drive_manager, mock_write_commit_file, + mock_backup, mock_backup_file_exits, mock_app_paths, mock_get_hashed_mnemonic, setup_directory, +): """Case 2: When backup not exits after api call""" - test_dir, _ = setup_directory + test_dir, backup_dir = setup_directory # Setup mocks mock_get_hashed_mnemonic.return_value = 'e23ddff3cc' - mock_get_path.return_value = test_dir + mock_app_paths.backup_folder_path = backup_dir + mock_app_paths.iriswallet_temp_folder_path = test_dir mock_backup_instance = MagicMock() mock_backup.return_value = None mock_backup_file_exits.return_value = False mock_backup_instance.return_value = None mock_google_drive_manager.return_value = mock_backup_instance - mock_backup_instance.upload_to_drive.return_value = True + mock_backup_instance.upload_to_drive.side_effect = [True, True] + mock_write_commit_file.return_value = ( + os.path.join( + backup_dir, 'e23ddff3cc.commit', + ), 'e23ddff3cc.commit', + ) error_message = ERROR_BACKUP_FILE_NOT_EXITS with pytest.raises(CommonException, match=error_message): BackupService.backup(mock_valid_mnemonic, mock_password) @patch('src.data.service.common_operation_service.CommonOperationService.get_hashed_mnemonic') -@patch('src.utils.local_store.local_store.get_path') +@patch('src.data.service.backup_service.app_paths') @patch('src.data.service.backup_service.BackupService.backup_file_exists') @patch('src.data.repository.common_operations_repository.CommonOperationRepository.backup') -def test_backup_no_mnemonic(mock_backup, mock_backup_file_exits, mock_get_path, mock_get_hashed_mnemonic): +def test_backup_no_mnemonic(mock_backup, mock_backup_file_exits, mock_app_paths, mock_get_hashed_mnemonic): """Case 3 : Test backup service with missing mnemonic""" # Setup mocks mock_get_hashed_mnemonic.side_effect = CommonException( ERROR_UNABLE_GET_MNEMONIC, ) - mock_get_path.return_value = os.path.join( + mock_app_paths.backup_folder_path = os.path.join( + os.path.dirname(__file__), 'some_path', + ) + mock_app_paths.iriswallet_temp_folder_path = os.path.join( os.path.dirname(__file__), 'some_path', ) mock_backup.return_value = None @@ -128,13 +150,16 @@ def test_backup_no_mnemonic(mock_backup, mock_backup_file_exits, mock_get_path, @patch('src.data.service.common_operation_service.CommonOperationService.get_hashed_mnemonic') -@patch('src.utils.local_store.local_store.get_path') -def test_backup_no_password(mock_get_path, mock_get_hashed_mnemonic): +@patch('src.data.service.backup_service.app_paths') +def test_backup_no_password(mock_app_paths, mock_get_hashed_mnemonic): """Case 4 : Test backup service with missing password""" # Setup mocks mock_get_hashed_mnemonic.return_value = 'e23ddff3cc' - mock_get_path.return_value = os.path.join( + mock_app_paths.backup_folder_path = os.path.join( + os.path.dirname(__file__), 'some_path', + ) + mock_app_paths.iriswallet_temp_folder_path = os.path.join( os.path.dirname(__file__), 'some_path', ) @@ -147,15 +172,22 @@ def test_backup_no_password(mock_get_path, mock_get_hashed_mnemonic): @patch('src.data.service.common_operation_service.CommonOperationService.get_hashed_mnemonic') +@patch('src.data.service.backup_service.app_paths') @patch('src.data.service.backup_service.BackupService.backup_file_exists') @patch('src.data.repository.common_operations_repository.CommonOperationRepository.backup') -def test_backup_no_hashed_value(mock_backup, mock_backup_file_exits, mock_get_hashed_mnemonic): +def test_backup_no_hashed_value(mock_backup, mock_backup_file_exits, mock_app_paths, mock_get_hashed_mnemonic): """Case 5 : Test backup service with missing hashed value""" # Setup mocks mock_get_hashed_mnemonic.side_effect = CommonException( ERROR_UNABLE_TO_GET_HASHED_MNEMONIC, ) + mock_app_paths.backup_folder_path = os.path.join( + os.path.dirname(__file__), 'some_path', + ) + mock_app_paths.iriswallet_temp_folder_path = os.path.join( + os.path.dirname(__file__), 'some_path', + ) mock_backup.return_value = None mock_backup_file_exits.return_value = True diff --git a/unit_tests/tests/service_tests/main_asset_page_service_test.py b/unit_tests/tests/service_tests/main_asset_page_service_test.py index cea0b506..2b638385 100644 --- a/unit_tests/tests/service_tests/main_asset_page_service_test.py +++ b/unit_tests/tests/service_tests/main_asset_page_service_test.py @@ -2,12 +2,6 @@ # pylint: disable=redefined-outer-name,unused-argument,too-many-arguments,unused-import from __future__ import annotations -from unittest import mock - -import pytest -from pytest_mock import mocker - -from src.data.repository.rgb_repository import RgbRepository from src.data.service.main_asset_page_service import MainAssetPageDataService from src.model.common_operation_model import MainPageDataResponseModel from src.model.enums.enums_model import FilterAssetEnumModel @@ -15,7 +9,6 @@ from src.model.rgb_model import FilterAssetRequestModel from src.model.rgb_model import RefreshTransferResponseModel from src.model.setting_model import IsHideExhaustedAssetEnabled -from src.utils.custom_exception import CommonException from unit_tests.repository_fixture.btc_repository_mock import mock_get_btc_balance from unit_tests.repository_fixture.rgb_repository_mock import mock_get_asset from unit_tests.repository_fixture.rgb_repository_mock import mock_refresh_transfer diff --git a/unit_tests/tests/service_tests/restore_test.py b/unit_tests/tests/service_tests/restore_test.py index a9ad0639..81952bb0 100644 --- a/unit_tests/tests/service_tests/restore_test.py +++ b/unit_tests/tests/service_tests/restore_test.py @@ -53,23 +53,29 @@ def teardown_directory_after_test(): # Test function +@patch('src.data.service.restore_service.COMPATIBLE_RLN_NODE_COMMITS', ['abc']) +@patch('src.data.service.restore_service.read_ln_node_commit_id_file') +@patch('src.data.service.restore_service.SettingRepository.set_rln_node_commit_id') @patch('src.data.service.common_operation_service.CommonOperationService.get_hashed_mnemonic') @patch('src.utils.local_store.local_store.get_path') @patch('src.data.repository.common_operations_repository.CommonOperationRepository.restore') @patch('src.data.service.restore_service.GoogleDriveManager') -def test_restore(mock_google_drive_manager, mock_restore, mock_get_path, mock_get_hashed_mnemonic, setup_directory): +def test_restore(mock_google_drive_manager, mock_restore, mock_app_paths, mock_get_hashed_mnemonic, mock_set_commit_id, mock_read_commit_id, setup_directory): """Case 1: Test restore service""" - test_dir, _ = setup_directory + test_dir, restore_dir = setup_directory # Setup mocks mock_get_hashed_mnemonic.return_value = 'e23ddff3cc' - mock_get_path.return_value = test_dir + mock_app_paths.restore_folder_path = restore_dir + mock_app_paths.iriswallet_temp_folder_path = test_dir + mock_read_commit_id.return_value = 'abc' mock_restore_instance = MagicMock() mock_restore.return_value = RestoreResponseModel(status=True) mock_restore_instance.return_value = None mock_google_drive_manager.return_value = mock_restore_instance - mock_restore_instance.download_from_drive.return_value = True + # Two downloads: commit id file, then backup file + mock_restore_instance.download_from_drive.side_effect = [True, True] result = RestoreService.restore(mock_valid_mnemonic, mock_password) @@ -79,23 +85,32 @@ def test_restore(mock_google_drive_manager, mock_restore, mock_get_path, mock_ge # Test function +@patch('src.data.service.restore_service.COMPATIBLE_RLN_NODE_COMMITS', ['abc']) +@patch('src.data.service.restore_service.read_ln_node_commit_id_file') +@patch('src.data.service.restore_service.SettingRepository.set_rln_node_commit_id') @patch('src.data.service.common_operation_service.CommonOperationService.get_hashed_mnemonic') @patch('src.utils.local_store.local_store.get_path') @patch('src.data.repository.common_operations_repository.CommonOperationRepository.restore') @patch('src.data.service.restore_service.GoogleDriveManager') -def test_restore_when_file_not_exists(mock_google_drive_manager, mock_restore, mock_get_path, mock_get_hashed_mnemonic, setup_directory): +def test_restore_when_file_not_exists( + mock_google_drive_manager, mock_restore, + mock_app_paths, mock_get_hashed_mnemonic, mock_set_commit_id, mock_read_commit_id, setup_directory, +): """Case 2: When restore file does not exist after download""" - test_dir, _ = setup_directory + test_dir, restore_dir = setup_directory # Setup mocks mock_get_hashed_mnemonic.return_value = 'e23ddff3cc' - mock_get_path.return_value = test_dir + mock_app_paths.restore_folder_path = restore_dir + mock_app_paths.iriswallet_temp_folder_path = test_dir + mock_read_commit_id.return_value = 'abc' mock_restore_instance = MagicMock() mock_restore.return_value = RestoreResponseModel(status=True) mock_restore_instance.return_value = None mock_google_drive_manager.return_value = mock_restore_instance - mock_restore_instance.download_from_drive.return_value = None + # First: commit id download succeeds, Second: backup download returns None + mock_restore_instance.download_from_drive.side_effect = [True, None] error_message = ERROR_NOT_BACKUP_FILE with pytest.raises(CommonException, match=error_message): @@ -103,16 +118,19 @@ def test_restore_when_file_not_exists(mock_google_drive_manager, mock_restore, m @patch('src.data.service.common_operation_service.CommonOperationService.get_hashed_mnemonic') -@patch('src.utils.local_store.local_store.get_path') +@patch('src.data.service.restore_service.app_paths') @patch('src.data.service.restore_service.GoogleDriveManager') @patch('src.data.repository.common_operations_repository.CommonOperationRepository.restore') -def test_restore_no_mnemonic(mock_restore, mock_google_drive_manager, mock_get_path, mock_get_hashed_mnemonic): +def test_restore_no_mnemonic(mock_restore, mock_google_drive_manager, mock_app_paths, mock_get_hashed_mnemonic): """Case 3: Test restore service with missing mnemonic""" # Setup mocks mock_get_hashed_mnemonic.side_effect = CommonException( ERROR_UNABLE_GET_MNEMONIC, ) - mock_get_path.return_value = os.path.join( + mock_app_paths.restore_folder_path = os.path.join( + os.path.dirname(__file__), 'some_path', + ) + mock_app_paths.iriswallet_temp_folder_path = os.path.join( os.path.dirname(__file__), 'some_path', ) mock_google_drive_manager.return_value = MagicMock() @@ -128,13 +146,16 @@ def test_restore_no_mnemonic(mock_restore, mock_google_drive_manager, mock_get_p @patch('src.data.service.common_operation_service.CommonOperationService.get_hashed_mnemonic') -@patch('src.utils.local_store.local_store.get_path') -def test_restore_no_password(mock_get_path, mock_get_hashed_mnemonic): +@patch('src.data.service.restore_service.app_paths') +def test_restore_no_password(mock_app_paths, mock_get_hashed_mnemonic): """Case 4: Test restore service with missing password""" # Setup mocks mock_get_hashed_mnemonic.return_value = 'e23ddff3cc' - mock_get_path.return_value = os.path.join( + mock_app_paths.restore_folder_path = os.path.join( + os.path.dirname(__file__), 'some_path', + ) + mock_app_paths.iriswallet_temp_folder_path = os.path.join( os.path.dirname(__file__), 'some_path', ) @@ -147,15 +168,22 @@ def test_restore_no_password(mock_get_path, mock_get_hashed_mnemonic): @patch('src.data.service.common_operation_service.CommonOperationService.get_hashed_mnemonic') +@patch('src.data.service.restore_service.app_paths') @patch('src.data.service.restore_service.GoogleDriveManager') @patch('src.data.repository.common_operations_repository.CommonOperationRepository.restore') -def test_restore_no_hashed_value(mock_restore, mock_google_drive_manager, mock_get_hashed_mnemonic): +def test_restore_no_hashed_value(mock_restore, mock_google_drive_manager, mock_app_paths, mock_get_hashed_mnemonic): """Case 5: Test restore service with missing hashed value""" # Setup mocks mock_get_hashed_mnemonic.side_effect = CommonException( ERROR_UNABLE_TO_GET_HASHED_MNEMONIC, ) + mock_app_paths.restore_folder_path = os.path.join( + os.path.dirname(__file__), 'some_path', + ) + mock_app_paths.iriswallet_temp_folder_path = os.path.join( + os.path.dirname(__file__), 'some_path', + ) mock_google_drive_manager.return_value = MagicMock() mock_google_drive_manager.return_value.download_from_drive.return_value = True mock_restore.return_value = RestoreResponseModel(status=True) @@ -165,15 +193,27 @@ def test_restore_no_hashed_value(mock_restore, mock_google_drive_manager, mock_g RestoreService.restore(mock_valid_mnemonic, mock_password) +@patch('src.data.service.restore_service.COMPATIBLE_RLN_NODE_COMMITS', ['abc']) +@patch('src.data.service.restore_service.read_ln_node_commit_id_file') @patch('src.data.service.common_operation_service.CommonOperationService.get_hashed_mnemonic') +@patch('src.data.service.restore_service.app_paths') @patch('src.data.service.restore_service.GoogleDriveManager') -def test_restore_download_error(mock_google_drive_manager, mock_get_hashed_mnemonic): +def test_restore_download_error(mock_google_drive_manager, mock_app_paths, mock_get_hashed_mnemonic, mock_read_commit_id): """Case 6: Test restore service with download failure""" # Setup mocks mock_get_hashed_mnemonic.return_value = 'e23ddff3cc' - mock_google_drive_manager.return_value = MagicMock() - mock_google_drive_manager.return_value.download_from_drive.return_value = False + mock_app_paths.restore_folder_path = os.path.join( + os.path.dirname(__file__), 'some_path', + ) + mock_app_paths.iriswallet_temp_folder_path = os.path.join( + os.path.dirname(__file__), 'some_path', + ) + mock_read_commit_id.return_value = 'abc' + drive = MagicMock() + mock_google_drive_manager.return_value = drive + # First commit id download ok, second backup download fails + drive.download_from_drive.side_effect = [True, False] # Call the RestoreService.restore method with pytest.raises(CommonException, match=ERROR_WHILE_RESTORE_DOWNLOAD_FROM_DRIVE): diff --git a/unit_tests/tests/service_tests/services_helper_test/faucet_service_helper_test.py b/unit_tests/tests/service_tests/services_helper_test/faucet_service_helper_test.py index c12a42ab..011ee1e0 100644 --- a/unit_tests/tests/service_tests/services_helper_test/faucet_service_helper_test.py +++ b/unit_tests/tests/service_tests/services_helper_test/faucet_service_helper_test.py @@ -9,6 +9,7 @@ from src.model.enums.enums_model import NetworkEnumModel from src.utils.constant import rgbMainnetFaucetURLs from src.utils.constant import rgbRegtestFaucetURLs +from src.utils.constant import rgbTestnet4FaucetURLs from src.utils.constant import rgbTestnetFaucetURLs from src.utils.custom_exception import ServiceOperationException from src.utils.error_message import ERROR_FAILED_TO_GET_FAUCET_URL @@ -21,9 +22,11 @@ def test_get_faucet_url(): """Case 1 : Test all network""" response_net_regtest = get_faucet_url(NetworkEnumModel.REGTEST) response_net_testnet = get_faucet_url(NetworkEnumModel.TESTNET) + response_net_testnet4 = get_faucet_url(NetworkEnumModel.TESTNET4) response_net_mainnet = get_faucet_url(NetworkEnumModel.MAINNET) assert response_net_mainnet == rgbMainnetFaucetURLs[0] assert response_net_testnet == rgbTestnetFaucetURLs[0] + assert response_net_testnet4 == rgbTestnet4FaucetURLs[0] assert response_net_regtest == rgbRegtestFaucetURLs[0] diff --git a/unit_tests/tests/service_tests/services_helper_test/main_asset_page_service_helper_test.py b/unit_tests/tests/service_tests/services_helper_test/main_asset_page_service_helper_test.py index c301ff86..df797b02 100644 --- a/unit_tests/tests/service_tests/services_helper_test/main_asset_page_service_helper_test.py +++ b/unit_tests/tests/service_tests/services_helper_test/main_asset_page_service_helper_test.py @@ -22,9 +22,11 @@ def test_get_offline_asset_ticker(): """Case 1 : Test all network""" response_net_regtest = get_offline_asset_ticker(NetworkEnumModel.REGTEST) response_net_testnet = get_offline_asset_ticker(NetworkEnumModel.TESTNET) + response_net_testnet4 = get_offline_asset_ticker(NetworkEnumModel.TESTNET4) response_net_mainnet = get_offline_asset_ticker(NetworkEnumModel.MAINNET) assert response_net_mainnet == 'BTC' assert response_net_testnet == 'tBTC' + assert response_net_testnet4 == 'tBTC' assert response_net_regtest == 'rBTC' diff --git a/unit_tests/tests/ui_tests/settings_helpers_test.py b/unit_tests/tests/ui_tests/settings_helpers_test.py new file mode 100644 index 00000000..4df68fad --- /dev/null +++ b/unit_tests/tests/ui_tests/settings_helpers_test.py @@ -0,0 +1,253 @@ +"""Unit tests for settings helper functions.""" +from __future__ import annotations + +from unittest.mock import MagicMock + +import pytest +from PySide6.QtWidgets import QDialog + +from src.model.enums.enums_model import NetworkEnumModel +from src.views.components.settings_helpers import check_keyring_state_for_password +from src.views.components.settings_helpers import set_endpoint_based_on_network +from src.views.components.settings_helpers import set_frame_content +from src.views.components.settings_helpers import update_save_button + + +def test_set_endpoint_based_on_network_mainnet(mocker): + """Test setting endpoints for mainnet network.""" + # Mock SettingRepository to return mainnet + mocker.patch( + 'src.views.components.settings_helpers.SettingRepository.get_wallet_network', + return_value=NetworkEnumModel.MAINNET, + ) + + # Call function + indexer_url, proxy_endpoint, bitcoind_host, bitcoind_port = set_endpoint_based_on_network() + + # Verify correct endpoints were returned + assert indexer_url == 'http://127.0.0.1:50003' + assert proxy_endpoint == 'http://127.0.0.1:3002/json-rpc' + assert bitcoind_host == 'localhost' + assert bitcoind_port == 18447 + + +def test_set_endpoint_based_on_network_testnet(mocker): + """Test setting endpoints for testnet network.""" + # Mock SettingRepository to return testnet + mocker.patch( + 'src.views.components.settings_helpers.SettingRepository.get_wallet_network', + return_value=NetworkEnumModel.TESTNET, + ) + + # Call function + indexer_url, proxy_endpoint, bitcoind_host, bitcoind_port = set_endpoint_based_on_network() + + # Verify correct endpoints were returned + assert indexer_url == 'ssl://electrum.iriswallet.com:50013' + assert proxy_endpoint == 'rpcs://proxy.iriswallet.com/0.2/json-rpc' + assert bitcoind_host == 'electrum.iriswallet.com' + assert bitcoind_port == 18332 + + +def test_set_endpoint_based_on_network_testnet4(mocker): + """Test setting endpoints for testnet4 network.""" + # Mock SettingRepository to return testnet4 + mocker.patch( + 'src.views.components.settings_helpers.SettingRepository.get_wallet_network', + return_value=NetworkEnumModel.TESTNET4, + ) + + # Call function + indexer_url, proxy_endpoint, bitcoind_host, bitcoind_port = set_endpoint_based_on_network() + + # Verify correct endpoints were returned + assert indexer_url == 'ssl://electrum.iriswallet.com:50053' + assert proxy_endpoint == 'rpcs://proxy.iriswallet.com/0.2/json-rpc' + assert bitcoind_host == 'electrum.iriswallet.com' + assert bitcoind_port == 48332 + + +def test_set_endpoint_based_on_network_regtest(mocker): + """Test setting endpoints for regtest network.""" + # Mock SettingRepository to return regtest + mocker.patch( + 'src.views.components.settings_helpers.SettingRepository.get_wallet_network', + return_value=NetworkEnumModel.REGTEST, + ) + + # Call function + indexer_url, proxy_endpoint, bitcoind_host, bitcoind_port = set_endpoint_based_on_network() + + # Verify correct endpoints were returned + assert indexer_url == 'electrum.rgbtools.org:50041' + assert proxy_endpoint == 'rpcs://proxy.iriswallet.com/0.2/json-rpc' + assert bitcoind_host == 'regtest-bitcoind.rgbtools.org' + assert bitcoind_port == 80 + + +def test_set_endpoint_based_on_network_invalid(mocker): + """Test setting endpoints with invalid network type.""" + # Mock SettingRepository to return invalid network + mocker.patch( + 'src.views.components.settings_helpers.SettingRepository.get_wallet_network', + return_value='invalid_network', + ) + + # Verify ValueError is raised + with pytest.raises(ValueError) as exc_info: + set_endpoint_based_on_network() + + assert 'Unsupported network type' in str(exc_info.value) + + +def test_set_frame_content_basic(): + """Test setting frame content with basic configuration.""" + # Create mock frame and components + mock_frame = MagicMock() + mock_frame.input_value = MagicMock() + mock_frame.suggestion_desc = MagicMock() + mock_frame.time_unit_combobox = MagicMock() + mock_frame.save_button = MagicMock() + + # Test with float input that's an integer + set_frame_content(mock_frame, 10.0, 'hours') + mock_frame.input_value.setText.assert_called_with('10') + mock_frame.input_value.setPlaceholderText.assert_called_with('10') + mock_frame.suggestion_desc.hide.assert_called_once() + mock_frame.time_unit_combobox.hide.assert_called_once() + + +def test_set_frame_content_with_validator(): + """Test setting frame content with validator.""" + mock_frame = MagicMock() + mock_validator = MagicMock() + + set_frame_content(mock_frame, 10, 'hours', validator=mock_validator) + mock_frame.input_value.setValidator.assert_called_with(mock_validator) + + +def test_set_frame_content_with_time_unit_combobox(): + """Test setting frame content with time unit combobox.""" + mock_frame = MagicMock() + mock_combobox = MagicMock() + mock_combobox.findText.return_value = 1 + + set_frame_content( + mock_frame, 10, 'hours', time_unit_combobox=mock_combobox, + ) + mock_combobox.setCurrentIndex.assert_called_with(1) + + +def test_update_save_button_value_changed(): + """Test updating save button when input value has changed.""" + mock_frame = MagicMock() + mock_frame.input_value.text.return_value = '20' + mock_frame.time_unit_combobox.currentText.return_value = 'hours' + + update_save_button(mock_frame, '10', 'hours') + mock_frame.save_button.setDisabled.assert_called_with(False) + + +def test_update_save_button_value_unchanged(): + """Test updating save button when input value hasn't changed.""" + mock_frame = MagicMock() + mock_frame.input_value.text.return_value = '10' + mock_frame.time_unit_combobox.currentText.return_value = 'hours' + + update_save_button(mock_frame, '10', 'hours') + mock_frame.save_button.setDisabled.assert_called_with(True) + + +def test_update_save_button_time_unit_changed(): + """Test updating save button with time unit change.""" + mock_frame = MagicMock() + mock_frame.input_value.text.return_value = '10' + mock_frame.time_unit_combobox.currentText.return_value = 'hours' + + update_save_button( + mock_frame, '10', 'minutes', mock_frame.time_unit_combobox, + ) + mock_frame.save_button.setDisabled.assert_called_with(False) + + +def test_check_keyring_state_disabled(mocker): + """Test checking keyring state when keyring is disabled.""" + # Mock dependencies + mocker.patch( + 'src.views.components.settings_helpers.SettingRepository.get_keyring_status', + return_value=False, + ) + mocker.patch( + 'src.views.components.settings_helpers.SettingRepository.get_wallet_network', + return_value=NetworkEnumModel.MAINNET, + ) + mock_get_value = mocker.patch( + 'src.views.components.settings_helpers.get_value', + return_value='test_password', + ) + + # Call function + result = check_keyring_state_for_password(None, None) + + # Verify password was retrieved from storage + mock_get_value.assert_called_once_with('wallet_password', 'mainnet') + assert result == 'test_password' + + +def test_check_keyring_state_enabled_accepted(mocker): + """Test checking keyring state when keyring is enabled and dialog is accepted.""" + # Mock dependencies + mocker.patch( + 'src.views.components.settings_helpers.SettingRepository.get_keyring_status', + return_value=True, + ) + mock_dialog = MagicMock() + mock_dialog.exec.return_value = QDialog.Accepted + mock_dialog.password_input.text.return_value = 'dialog_password' + mock_dialog_class = mocker.patch( + 'src.views.components.settings_helpers.RestoreMnemonicWidget', + return_value=mock_dialog, + ) + + # Call function + parent_mock = MagicMock() + view_model_mock = MagicMock() + result = check_keyring_state_for_password(parent_mock, view_model_mock) + + # Verify dialog was created with correct parameters + mock_dialog_class.assert_called_once_with( + parent=parent_mock, + view_model=view_model_mock, + origin_page='setting_card', + mnemonic_visibility=False, + ) + + # Verify dialog was configured correctly + mock_dialog.mnemonic_detail_text_label.setText.assert_called_once() + mock_dialog.mnemonic_detail_text_label.setFixedHeight.assert_called_once_with( + 40, + ) + + # Verify password was retrieved from dialog + assert result == 'dialog_password' + + +def test_check_keyring_state_enabled_rejected(mocker): + """Test checking keyring state when keyring is enabled and dialog is rejected.""" + # Mock dependencies + mocker.patch( + 'src.views.components.settings_helpers.SettingRepository.get_keyring_status', + return_value=True, + ) + mock_dialog = MagicMock() + mock_dialog.exec.return_value = QDialog.Rejected + mocker.patch( + 'src.views.components.settings_helpers.RestoreMnemonicWidget', + return_value=mock_dialog, + ) + + # Call function + result = check_keyring_state_for_password(None, None) + + # Verify None is returned when dialog is rejected + assert result is None diff --git a/unit_tests/tests/ui_tests/ui_collectible_asset_test.py b/unit_tests/tests/ui_tests/ui_collectible_asset_test.py index 4354c437..d7aae010 100644 --- a/unit_tests/tests/ui_tests/ui_collectible_asset_test.py +++ b/unit_tests/tests/ui_tests/ui_collectible_asset_test.py @@ -20,6 +20,7 @@ from PySide6.QtWidgets import QScrollArea from PySide6.QtWidgets import QWidget +from src.model.enums.enums_model import AssetType from src.model.enums.enums_model import ToastPreset from src.model.rgb_model import RgbAssetPageLoadModel from src.utils.constant import IRIS_WALLET_TRANSLATIONS_CONTEXT @@ -71,7 +72,6 @@ def test_create_collectible_frame_with_image(collectible_asset_widget, mocker): coll_asset.name = 'Mock Asset' coll_asset.media.file_path = 'mock_image_path' coll_asset.media.hex = None # Valid image path, no hex - coll_asset.asset_iface = 'mock_iface' # Mock the resize_image method to return a dummy QPixmap mocker.patch('src.utils.common_utils.resize_image', return_value=QPixmap()) @@ -126,7 +126,6 @@ def test_create_collectible_frame_with_empty_name(collectible_asset_widget, mock coll_asset.name = '' # Empty name coll_asset.media.file_path = 'mock_image_path' coll_asset.media.hex = None - coll_asset.asset_iface = 'mock_iface' # Mock the resize_image method to return a dummy QPixmap mocker.patch('src.utils.common_utils.resize_image', return_value=QPixmap()) @@ -139,27 +138,6 @@ def test_create_collectible_frame_with_empty_name(collectible_asset_widget, mock assert asset_name_label.text() == '' # The label should display an empty name -def test_create_collectible_frame_with_different_asset_type(collectible_asset_widget, mocker): - """Test the creation of a collectible frame with a different asset type.""" - - # Mock a collectible asset with a different asset type - coll_asset = MagicMock() - coll_asset.asset_id = 'mock_id' - coll_asset.name = 'Mock Asset' - coll_asset.media.file_path = 'mock_image_path' - coll_asset.media.hex = None - coll_asset.asset_iface = 'different_type' # Different asset type - - # Mock the resize_image method to return a dummy QPixmap - mocker.patch('src.utils.common_utils.resize_image', return_value=QPixmap()) - - # Call the create_collectible_frame method - frame = collectible_asset_widget.create_collectible_frame(coll_asset) - - # Assert that the asset type is correctly passed and used - assert frame._asset_type == 'different_type' - - def test_create_collectible_frame_edge_case(collectible_asset_widget, mocker): """Test the creation of a collectible frame with edge case values (e.g., very large image path).""" @@ -169,7 +147,6 @@ def test_create_collectible_frame_edge_case(collectible_asset_widget, mocker): coll_asset.name = 'Edge Case Asset' coll_asset.media.file_path = 'a' * 1000 # Very large file path coll_asset.media.hex = None - coll_asset.asset_iface = 'mock_iface' # Mock the resize_image method to return a dummy QPixmap mocker.patch('src.utils.common_utils.resize_image', return_value=QPixmap()) @@ -202,7 +179,6 @@ def test_create_collectible_frame(collectible_asset_widget, mocker): coll_asset.name = 'Mock Asset' coll_asset.media.file_path = 'mock_path' coll_asset.media.hex = None - coll_asset.asset_iface = 'mock_iface' # Mock the resize_image and convert_hex_to_image methods mocker.patch('src.utils.common_utils.resize_image', return_value=QPixmap()) @@ -218,7 +194,7 @@ def test_create_collectible_frame(collectible_asset_widget, mocker): QLabel, 'collectible_asset_name', ).text() == 'Mock Asset' assert frame.findChild(QLabel, 'collectible_image').pixmap() is not None - assert frame._asset_type == 'mock_iface' + assert frame._asset_type == AssetType.CFA assert frame.cursor().shape() == Qt.CursorShape.PointingHandCursor assert frame.styleSheet() == ( 'background: transparent;\n' @@ -336,17 +312,17 @@ def test_handle_collectible_frame_click(collectible_asset_widget, mocker): asset_type = 'mock_type' mock_view_model = collectible_asset_widget._view_model - mock_view_model.rgb25_view_model.asset_info.emit = MagicMock() - mock_view_model.page_navigation.rgb25_detail_page = MagicMock() + mock_view_model.cfa_view_model.asset_info.emit = MagicMock() + mock_view_model.page_navigation.cfa_detail_page = MagicMock() collectible_asset_widget.handle_collectible_frame_click( asset_id, asset_name, image_path, asset_type, ) - mock_view_model.rgb25_view_model.asset_info.emit.assert_called_once_with( + mock_view_model.cfa_view_model.asset_info.emit.assert_called_once_with( asset_id, asset_name, image_path, asset_type, ) - mock_view_model.page_navigation.rgb25_detail_page.assert_called_once_with( + mock_view_model.page_navigation.cfa_detail_page.assert_called_once_with( RgbAssetPageLoadModel( asset_id=None, asset_name=None, image_path=None, asset_type='mock_type', @@ -396,7 +372,6 @@ def test_create_collectible_frame_image_loading_failure(collectible_asset_widget coll_asset.name = 'Mock Asset' coll_asset.media.file_path = 'mock_path' coll_asset.media.hex = None - coll_asset.asset_iface = 'mock_iface' # Simulate image loading failure mocker.patch('src.utils.common_utils.resize_image', return_value=None) @@ -417,16 +392,16 @@ def test_handle_collectible_frame_click_invalid_data(collectible_asset_widget): # Mock view model methods to ensure they aren't called mock_view_model = collectible_asset_widget._view_model - mock_view_model.rgb25_view_model.asset_info.emit = MagicMock() - mock_view_model.page_navigation.rgb25_detail_page = MagicMock() + mock_view_model.cfa_view_model.asset_info.emit = MagicMock() + mock_view_model.page_navigation.cfa_detail_page = MagicMock() collectible_asset_widget.handle_collectible_frame_click( asset_id, asset_name, image_path, asset_type, ) # Ensure that no method was called with invalid asset ID - mock_view_model.rgb25_view_model.asset_info.emit.assert_not_called() - mock_view_model.page_navigation.rgb25_detail_page.assert_not_called() + mock_view_model.cfa_view_model.asset_info.emit.assert_not_called() + mock_view_model.page_navigation.cfa_detail_page.assert_not_called() # Negative test case: Test failure during asset loading diff --git a/unit_tests/tests/ui_tests/ui_create_ln_invoice_test.py b/unit_tests/tests/ui_tests/ui_create_ln_invoice_test.py index 7de08688..934202fc 100644 --- a/unit_tests/tests/ui_tests/ui_create_ln_invoice_test.py +++ b/unit_tests/tests/ui_tests/ui_create_ln_invoice_test.py @@ -160,7 +160,7 @@ def test_get_ln_invoice(mock_create_ln_invoice_view_model, create_ln_invoice_wid # Test page navigation after invoice creation mock_page_navigation = MagicMock() - widget._view_model.page_navigation.receive_rgb25_page = mock_page_navigation + widget._view_model.page_navigation.receive_cfa_page = mock_page_navigation widget.get_ln_invoice() mock_page_navigation.assert_called_once() @@ -431,9 +431,9 @@ def test_on_close(create_ln_invoice_widget): create_ln_invoice_widget._view_model.page_navigation.collectibles_asset_page = MagicMock() create_ln_invoice_widget._view_model.page_navigation.fungibles_asset_page = MagicMock() - # Test case when asset_type is RGB25 (should navigate to collectibles_asset_page) - # Assuming AssetType.RGB25.value is 'RGB25' - create_ln_invoice_widget.asset_type = 'RGB25' + # Test case when asset_type is CFA (should navigate to collectibles_asset_page) + # Assuming AssetType.CFA.value is 'CFA' + create_ln_invoice_widget.asset_type = 'CFA' create_ln_invoice_widget.on_close() # Check that collectibles_asset_page is called and fungibles_asset_page is not @@ -444,8 +444,8 @@ def test_on_close(create_ln_invoice_widget): create_ln_invoice_widget._view_model.page_navigation.collectibles_asset_page.reset_mock() create_ln_invoice_widget._view_model.page_navigation.fungibles_asset_page.reset_mock() - # Test case when asset_type is not RGB25 (should navigate to fungibles_asset_page) - # Any asset type other than 'RGB25' + # Test case when asset_type is not CFA (should navigate to fungibles_asset_page) + # Any asset type other than 'CFA' create_ln_invoice_widget.asset_type = 'OTHER_ASSET' create_ln_invoice_widget.on_close() diff --git a/unit_tests/tests/ui_tests/ui_fungible_asset_test.py b/unit_tests/tests/ui_tests/ui_fungible_asset_test.py index 40f646dd..806dc8a8 100644 --- a/unit_tests/tests/ui_tests/ui_fungible_asset_test.py +++ b/unit_tests/tests/ui_tests/ui_fungible_asset_test.py @@ -37,7 +37,6 @@ def mock_fungible_asset_view_model(): mock_view_model.main_asset_view_model.assets.vanilla = MagicMock( asset_id='1', name='Bitcoin', - asset_iface=AssetType.BITCOIN.value, ticker=TokenSymbol.BITCOIN.value, balance=MagicMock(future=0.5), ) @@ -122,7 +121,6 @@ def test_ui_update_after_asset_loading(create_fungible_asset_widget: FungibleAss bitcoin_mock.name = 'bitcoin' # Set as string bitcoin_mock.balance = MagicMock() bitcoin_mock.balance.future = '0.5' - bitcoin_mock.asset_iface = 'BTC' # Mock assets in the view model widget._view_model.main_asset_view_model.assets.vanilla = bitcoin_mock @@ -149,7 +147,6 @@ def test_show_assets_with_various_assets(create_fungible_asset_widget, qtbot): bitcoin_mock.asset_id = 'btc_asset_id' bitcoin_mock.name = 'Bitcoin' bitcoin_mock.balance.future = '0.5' - bitcoin_mock.asset_iface = 'BTC' bitcoin_mock.ticker = 'BTC' # Mock NIA asset @@ -157,7 +154,6 @@ def test_show_assets_with_various_assets(create_fungible_asset_widget, qtbot): nia_mock.asset_id = 'nia_asset_id' nia_mock.name = 'NIA' nia_mock.balance.future = '1.0' - nia_mock.asset_iface = 'NIA' nia_mock.ticker = 'NIA' # Mock assets in the view model @@ -417,7 +413,7 @@ def test_handle_asset_frame_click(create_fungible_asset_widget): # Mock the view model and navigation methods widget._view_model.page_navigation = MagicMock() - widget._view_model.rgb25_view_model = MagicMock() + widget._view_model.cfa_view_model = MagicMock() # Test for Bitcoin asset type bitcoin_asset_id = 'btc_asset_id' @@ -434,8 +430,8 @@ def test_handle_asset_frame_click(create_fungible_asset_widget): # Verify navigation to Bitcoin page widget._view_model.page_navigation.bitcoin_page.assert_called_once() - widget._view_model.rgb25_view_model.asset_info.emit.assert_not_called() - widget._view_model.page_navigation.rgb25_detail_page.assert_not_called() + widget._view_model.cfa_view_model.asset_info.emit.assert_not_called() + widget._view_model.page_navigation.cfa_detail_page.assert_not_called() # Reset mocks for the next scenario widget._view_model.page_navigation.bitcoin_page.reset_mock() @@ -444,7 +440,7 @@ def test_handle_asset_frame_click(create_fungible_asset_widget): rgb_asset_id = 'rgb_asset_id' rgb_asset_name = 'RGB Asset' rgb_image_path = ':/assets/rgb.png' - rgb_asset_type = AssetType.RGB25.value + rgb_asset_type = AssetType.CFA.value widget.handle_asset_frame_click( asset_id=rgb_asset_id, @@ -454,12 +450,12 @@ def test_handle_asset_frame_click(create_fungible_asset_widget): ) # Verify asset_info signal is emitted with correct parameters - widget._view_model.rgb25_view_model.asset_info.emit.assert_called_once_with( + widget._view_model.cfa_view_model.asset_info.emit.assert_called_once_with( rgb_asset_id, rgb_asset_name, rgb_image_path, rgb_asset_type, ) # Verify navigation to RGB detail page - widget._view_model.page_navigation.rgb25_detail_page.assert_called_once_with( + widget._view_model.page_navigation.cfa_detail_page.assert_called_once_with( RgbAssetPageLoadModel(asset_type=rgb_asset_type), ) @@ -480,14 +476,11 @@ def test_create_fungible_card(create_fungible_asset_widget, qtbot): asset = MagicMock() asset.asset_id = 'sample_asset_id' asset.name = 'Sample Asset' - asset.asset_iface = AssetType.RGB20 asset.balance.future = 1000 asset.balance.offchain_outbound = 200 asset.ticker = 'SAMPLE' - # Call the method with and without an image path widget.create_fungible_card(asset) - widget.create_fungible_card(asset, img_path=':/assets/sample_icon.png') # Verify the fungible_frame is created with the correct settings assert widget.fungible_frame is not None @@ -499,23 +492,24 @@ def test_create_fungible_card(create_fungible_asset_widget, qtbot): assert widget.asset_name.text() == asset.name assert widget.asset_name.minimumSize() == QSize(135, 40) - # Verify address is set correctly for RGB20 + # Verify address is set correctly for NIA assert widget.address.text() == asset.asset_id # Verify amount is set assert widget.amount.text() == str(asset.balance.future) - # Verify outbound_balance is set for RGB20 + # Verify outbound_balance is set for NIA assert widget.outbound_balance.text() == str(asset.balance.offchain_outbound) # Verify token_symbol is set assert widget.token_symbol.text() == asset.ticker + widget.create_fungible_card(asset, img_path=':/assets/sample_icon.png') + # Verify that the fungible frame is added to the layout widget.vertical_layout_3.addWidget.assert_called() # Test for Bitcoin-specific behavior - asset.asset_iface = AssetType.BITCOIN asset.name = 'Bitcoin' # Test for BTC (mainnet Bitcoin) @@ -524,21 +518,32 @@ def test_create_fungible_card(create_fungible_asset_widget, qtbot): assert widget.token_symbol.text() == TokenSymbol.SAT.value assert widget.asset_name.text() == AssetType.BITCOIN.value.lower() + # Test for TESTNET4_BITCOIN + with patch('src.views.ui_fungible_asset.SettingRepository.get_wallet_network', return_value=NetworkEnumModel.TESTNET4): + asset.ticker = TokenSymbol.TESTNET4_BITCOIN.value + widget.create_fungible_card(asset) + assert widget.token_symbol.text() == TokenSymbol.SAT.value + assert widget.asset_name.text() == f'{NetworkEnumModel.TESTNET4.value} { + AssetType.BITCOIN.value.lower() + }' + # Test for TESTNET_BITCOIN - asset.ticker = TokenSymbol.TESTNET_BITCOIN.value - widget.create_fungible_card(asset) - assert widget.token_symbol.text() == TokenSymbol.SAT.value - assert widget.asset_name.text() == f'{NetworkEnumModel.TESTNET.value} { - AssetType.BITCOIN.value.lower() - }' + with patch('src.views.ui_fungible_asset.SettingRepository.get_wallet_network', return_value=NetworkEnumModel.TESTNET): + asset.ticker = TokenSymbol.TESTNET_BITCOIN.value + widget.create_fungible_card(asset) + assert widget.token_symbol.text() == TokenSymbol.SAT.value + assert widget.asset_name.text() == f'{NetworkEnumModel.TESTNET.value} { + AssetType.BITCOIN.value.lower() + }' # Test for REGTEST_BITCOIN - asset.ticker = TokenSymbol.REGTEST_BITCOIN.value - widget.create_fungible_card(asset) - assert widget.token_symbol.text() == TokenSymbol.SAT.value - assert widget.asset_name.text() == f'{NetworkEnumModel.REGTEST.value} { - AssetType.BITCOIN.value.lower() - }' + with patch('src.views.ui_fungible_asset.SettingRepository.get_wallet_network', return_value=NetworkEnumModel.REGTEST): + asset.ticker = TokenSymbol.REGTEST_BITCOIN.value + widget.create_fungible_card(asset) + assert widget.token_symbol.text() == TokenSymbol.SAT.value + assert widget.asset_name.text() == f'{NetworkEnumModel.REGTEST.value} { + AssetType.BITCOIN.value.lower() + }' def test_show_assets(create_fungible_asset_widget, qtbot): @@ -571,7 +576,6 @@ def test_show_assets(create_fungible_asset_widget, qtbot): asset = MagicMock() asset.name = 'Bitcoin' # Mock the 'name' attribute to return a string asset.asset_id = 'asset123' # Mock the 'asset_id' attribute - asset.asset_iface = 'bitcoin_iface' # Mock the 'asset_iface' attribute asset.ticker = 'BTC' # Mock the assets in the view model (if needed) diff --git a/unit_tests/tests/ui_tests/ui_issue_rgb25_test.py b/unit_tests/tests/ui_tests/ui_issue_cfa_test.py similarity index 62% rename from unit_tests/tests/ui_tests/ui_issue_rgb25_test.py rename to unit_tests/tests/ui_tests/ui_issue_cfa_test.py index cc1bb11d..48e5013e 100644 --- a/unit_tests/tests/ui_tests/ui_issue_rgb25_test.py +++ b/unit_tests/tests/ui_tests/ui_issue_cfa_test.py @@ -1,4 +1,4 @@ -"""Unit test for Issue RGB25 UI.""" +"""Unit test for Issue CFA UI.""" # Disable the redefined-outer-name warning as # it's normal to pass mocked objects in test functions # pylint: disable=redefined-outer-name,unused-argument,protected-access @@ -15,43 +15,43 @@ from src.model.common_operation_model import NodeInfoResponseModel from src.utils.constant import IRIS_WALLET_TRANSLATIONS_CONTEXT from src.viewmodels.main_view_model import MainViewModel -from src.views.ui_issue_rgb25 import IssueRGB25Widget +from src.views.ui_issue_cfa import IssueCFAWidget from unit_tests.tests.ui_tests.ui_helper_test.issue_asset_helper_test import assert_success_page_called @pytest.fixture -def issue_rgb25_page_navigation(): +def issue_cfa_page_navigation(): """Fixture to create a mocked page navigation object.""" mock_navigation = MagicMock() return mock_navigation @pytest.fixture -def mock_issue_rgb25_view_model(issue_rgb25_page_navigation: MagicMock): +def mock_issue_cfa_view_model(issue_cfa_page_navigation: MagicMock): """Fixture to create a MainViewModel instance with mocked page navigation.""" - return MainViewModel(issue_rgb25_page_navigation) + return MainViewModel(issue_cfa_page_navigation) @pytest.fixture -def issue_rgb25_widget(mock_issue_rgb25_view_model: MainViewModel): - """Fixture to create a IssueRGB25Widget instance.""" +def issue_cfa_widget(mock_issue_cfa_view_model: MainViewModel): + """Fixture to create a IssueCFAWidget instance.""" - return IssueRGB25Widget(mock_issue_rgb25_view_model) + return IssueCFAWidget(mock_issue_cfa_view_model) -def test_retranslate_ui(issue_rgb25_widget: IssueRGB25Widget): +def test_retranslate_ui(issue_cfa_widget: IssueCFAWidget): """Test that the UI strings are correctly translated.""" - issue_rgb25_widget.retranslate_ui() - assert issue_rgb25_widget.total_supply_label.text() == 'total_supply' - assert issue_rgb25_widget.asset_name_label.text() == 'asset_name' + issue_cfa_widget.retranslate_ui() + assert issue_cfa_widget.total_supply_label.text() == 'total_supply' + assert issue_cfa_widget.asset_name_label.text() == 'asset_name' -def test_on_issue_rgb25(issue_rgb25_widget: IssueRGB25Widget, qtbot): - """Test the on_issue_rgb25 method.""" - widget = issue_rgb25_widget +def test_on_issue_cfa(issue_cfa_widget: IssueCFAWidget, qtbot): + """Test the on_issue_cfa method.""" + widget = issue_cfa_widget # Mock the view model method - widget._view_model.issue_rgb25_asset_view_model.issue_rgb25_asset = MagicMock() + widget._view_model.issue_cfa_asset_view_model.issue_cfa_asset = MagicMock() # Set input values widget.asset_description_input.setText('Description') @@ -59,31 +59,31 @@ def test_on_issue_rgb25(issue_rgb25_widget: IssueRGB25Widget, qtbot): widget.amount_input.setText('1000') # Simulate the button click - widget.on_issue_rgb25() + widget.on_issue_cfa() # Verify that the view model method is called with the correct arguments - widget._view_model.issue_rgb25_asset_view_model.issue_rgb25_asset.assert_called_once_with( + widget._view_model.issue_cfa_asset_view_model.issue_cfa_asset.assert_called_once_with( 'Description', 'Asset Name', '1000', ) -def test_on_upload_asset_file(issue_rgb25_widget: IssueRGB25Widget, qtbot): +def test_on_upload_asset_file(issue_cfa_widget: IssueCFAWidget, qtbot): """Test the on_upload_asset_file method.""" - widget = issue_rgb25_widget + widget = issue_cfa_widget # Mock the view model method - widget._view_model.issue_rgb25_asset_view_model.open_file_dialog = MagicMock() + widget._view_model.issue_cfa_asset_view_model.open_file_dialog = MagicMock() # Simulate the button click widget.on_upload_asset_file() # Verify that the file dialog is opened - widget._view_model.issue_rgb25_asset_view_model.open_file_dialog.assert_called_once() + widget._view_model.issue_cfa_asset_view_model.open_file_dialog.assert_called_once() -def test_on_close(issue_rgb25_widget: IssueRGB25Widget, qtbot): +def test_on_close(issue_cfa_widget: IssueCFAWidget, qtbot): """Test the on_close method.""" - widget = issue_rgb25_widget + widget = issue_cfa_widget # Mock the page navigation method widget._view_model.page_navigation.collectibles_asset_page = MagicMock() @@ -95,15 +95,15 @@ def test_on_close(issue_rgb25_widget: IssueRGB25Widget, qtbot): widget._view_model.page_navigation.collectibles_asset_page.assert_called_once() -def test_handle_button_enabled(issue_rgb25_widget: IssueRGB25Widget, qtbot): +def test_handle_button_enabled(issue_cfa_widget: IssueCFAWidget, qtbot): """Test the handle_button_enabled method.""" - widget = issue_rgb25_widget + widget = issue_cfa_widget # Mock the inputs and button widget.amount_input = MagicMock() widget.asset_description_input = MagicMock() widget.name_of_the_asset_input = MagicMock() - widget.issue_rgb25_button = MagicMock() + widget.issue_cfa_button = MagicMock() # Case when all fields are filled widget.amount_input.text.return_value = '1000' @@ -111,38 +111,38 @@ def test_handle_button_enabled(issue_rgb25_widget: IssueRGB25Widget, qtbot): widget.name_of_the_asset_input.text.return_value = 'Asset Name' widget.handle_button_enabled() - widget.issue_rgb25_button.setDisabled.assert_called_once_with(False) + widget.issue_cfa_button.setDisabled.assert_called_once_with(False) # Case when one of the fields is empty widget.name_of_the_asset_input.text.return_value = '' widget.handle_button_enabled() - assert widget.issue_rgb25_button.isEnabled() + assert widget.issue_cfa_button.isEnabled() -def test_update_loading_state(issue_rgb25_widget: IssueRGB25Widget, qtbot): +def test_update_loading_state(issue_cfa_widget: IssueCFAWidget, qtbot): """Test the update_loading_state method.""" - widget = issue_rgb25_widget + widget = issue_cfa_widget # Mock the button's loading methods - widget.issue_rgb25_button.start_loading = MagicMock() - widget.issue_rgb25_button.stop_loading = MagicMock() + widget.issue_cfa_button.start_loading = MagicMock() + widget.issue_cfa_button.stop_loading = MagicMock() # Test loading state true widget.update_loading_state(True) - widget.issue_rgb25_button.start_loading.assert_called_once() - widget.issue_rgb25_button.stop_loading.assert_not_called() + widget.issue_cfa_button.start_loading.assert_called_once() + widget.issue_cfa_button.stop_loading.assert_not_called() # Test loading state false widget.update_loading_state(False) # still called once from previous - widget.issue_rgb25_button.start_loading.assert_called_once() - widget.issue_rgb25_button.stop_loading.assert_called_once() + widget.issue_cfa_button.start_loading.assert_called_once() + widget.issue_cfa_button.stop_loading.assert_called_once() -def test_show_asset_issued(issue_rgb25_widget: IssueRGB25Widget, qtbot): +def test_show_asset_issued(issue_cfa_widget: IssueCFAWidget, qtbot): """Test the show_asset_issued method.""" - widget = issue_rgb25_widget + widget = issue_cfa_widget # Mock the success page method widget._view_model.page_navigation.show_success_page = MagicMock() @@ -160,11 +160,11 @@ def test_show_asset_issued(issue_rgb25_widget: IssueRGB25Widget, qtbot): assert params.callback == widget._view_model.page_navigation.collectibles_asset_page -@patch('src.views.ui_issue_rgb25.os.path.getsize') -@patch('src.views.ui_issue_rgb25.resize_image') -@patch('src.views.ui_issue_rgb25.QPixmap') -@patch('src.views.ui_issue_rgb25.NodeInfoModel') -def test_show_file_preview(mock_node_info_model, mock_qpix_map, mock_resize_image, mock_getsize, issue_rgb25_widget): +@patch('src.views.ui_issue_cfa.os.path.getsize') +@patch('src.views.ui_issue_cfa.resize_image') +@patch('src.views.ui_issue_cfa.QPixmap') +@patch('src.views.ui_issue_cfa.NodeInfoModel') +def test_show_file_preview(mock_node_info_model, mock_qpix_map, mock_resize_image, mock_getsize, issue_cfa_widget): """Test the show_file_preview method.""" # Mock the NodeInfoModel to return a max file size of 10MB @@ -173,10 +173,10 @@ def test_show_file_preview(mock_node_info_model, mock_qpix_map, mock_resize_imag mock_node_info.node_info = MagicMock(spec=NodeInfoResponseModel) mock_node_info.node_info.max_media_upload_size_mb = 10 # 10MB max size - issue_rgb25_widget.file_path = MagicMock() - issue_rgb25_widget.issue_rgb25_button = MagicMock() - issue_rgb25_widget.issue_rgb_25_card = MagicMock() - issue_rgb25_widget.upload_file = MagicMock() + issue_cfa_widget.file_path = MagicMock() + issue_cfa_widget.issue_cfa_button = MagicMock() + issue_cfa_widget.issue_cfa_card = MagicMock() + issue_cfa_widget.upload_file = MagicMock() # Mock the file size returned by os.path.getsize # 15MB (larger than the allowed 10MB) @@ -190,22 +190,22 @@ def test_show_file_preview(mock_node_info_model, mock_qpix_map, mock_resize_imag file_upload_message = 'path/to/file.jpg' # Call the method under test - issue_rgb25_widget.show_file_preview(file_upload_message) + issue_cfa_widget.show_file_preview(file_upload_message) # Assert that the validation message is shown for large files expected_validation_text = QCoreApplication.translate( IRIS_WALLET_TRANSLATIONS_CONTEXT, 'image_validation', None, ).format(mock_node_info.node_info.max_media_upload_size_mb) - issue_rgb25_widget.file_path.setText.assert_called_once_with( + issue_cfa_widget.file_path.setText.assert_called_once_with( expected_validation_text, ) - issue_rgb25_widget.issue_rgb25_button.setDisabled.assert_any_call( + issue_cfa_widget.issue_cfa_button.setDisabled.assert_any_call( True, ) # Assert it was disabled first # Assert that the card's maximum size is set to (499, 608) - issue_rgb25_widget.issue_rgb_25_card.setMaximumSize.assert_called_once_with( + issue_cfa_widget.issue_cfa_card.setMaximumSize.assert_called_once_with( QSize(499, 608), ) @@ -213,19 +213,19 @@ def test_show_file_preview(mock_node_info_model, mock_qpix_map, mock_resize_imag mock_getsize.return_value = 5 * 1024 * 1024 # 5MB (valid size) # Call the method again with a smaller file size - issue_rgb25_widget.show_file_preview(file_upload_message) + issue_cfa_widget.show_file_preview(file_upload_message) # Assert that the file path is displayed as the uploaded file - issue_rgb25_widget.file_path.setText.assert_called_with( + issue_cfa_widget.file_path.setText.assert_called_with( file_upload_message, ) - issue_rgb25_widget.issue_rgb25_button.setDisabled.assert_any_call( + issue_cfa_widget.issue_cfa_button.setDisabled.assert_any_call( False, ) # Assert it was enabled later # Assert that the card's maximum size is set to (499, 808) - issue_rgb25_widget.issue_rgb_25_card.setMaximumSize.assert_called_with( + issue_cfa_widget.issue_cfa_card.setMaximumSize.assert_called_with( QSize(499, 808), ) @@ -233,12 +233,12 @@ def test_show_file_preview(mock_node_info_model, mock_qpix_map, mock_resize_imag mock_resize_image.assert_called_once_with(file_upload_message, 242, 242) # Assert that the resized image is set to the file path as a pixmap - issue_rgb25_widget.file_path.setPixmap.assert_called_once_with( + issue_cfa_widget.file_path.setPixmap.assert_called_once_with( mock_qpix_map.return_value, ) # Assert that the "change uploaded file" text is set - issue_rgb25_widget.upload_file.setText.assert_called_once_with( + issue_cfa_widget.upload_file.setText.assert_called_once_with( QCoreApplication.translate( IRIS_WALLET_TRANSLATIONS_CONTEXT, 'change_uploaded_file', 'CHANGE UPLOADED FILE', ), diff --git a/unit_tests/tests/ui_tests/ui_issue_nia_test.py b/unit_tests/tests/ui_tests/ui_issue_nia_test.py new file mode 100644 index 00000000..3147d9c3 --- /dev/null +++ b/unit_tests/tests/ui_tests/ui_issue_nia_test.py @@ -0,0 +1,155 @@ +"""Unit test for Issue NIA UI.""" +# Disable the redefined-outer-name warning as +# it's normal to pass mocked objects in test functions +# pylint: disable=redefined-outer-name,unused-argument,protected-access +from __future__ import annotations + +from unittest.mock import MagicMock + +import pytest + +from src.viewmodels.main_view_model import MainViewModel +from src.views.ui_issue_nia import IssueNIAWidget +from unit_tests.tests.ui_tests.ui_helper_test.issue_asset_helper_test import assert_success_page_called + + +@pytest.fixture +def issue_nia_page_navigation(): + """Fixture to create a mocked page navigation object.""" + mock_navigation = MagicMock() + return mock_navigation + + +@pytest.fixture +def mock_issue_nia_view_model(issue_nia_page_navigation: MagicMock): + """Fixture to create a MainViewModel instance with mocked page navigation.""" + return MainViewModel(issue_nia_page_navigation) + + +@pytest.fixture +def issue_nia_widget(mock_issue_nia_view_model: MainViewModel): + """Fixture to create a IssueNIAWidget instance.""" + return IssueNIAWidget(mock_issue_nia_view_model) + + +def test_retranslate_ui(issue_nia_widget: IssueNIAWidget): + """Test that the UI strings are correctly translated.""" + issue_nia_widget.retranslate_ui() + + assert issue_nia_widget.asset_ticker_label.text() == 'asset_ticker' + assert issue_nia_widget.asset_name_label.text() == 'asset_name' + + +def test_on_issue_nia_click(issue_nia_widget: IssueNIAWidget, qtbot): + """Test the on_issue_nia_click method.""" + widget = issue_nia_widget + + # Mock the input fields + widget.short_identifier_input = MagicMock() + widget.short_identifier_input.text.return_value = 'TTK' + + widget.asset_name_input = MagicMock() + widget.asset_name_input.text.return_value = 'NIA' + + widget.amount_input = MagicMock() + widget.amount_input.text.return_value = '100' + + # Mock the view model method + widget._view_model.issue_nia_asset_view_model.on_issue_click = MagicMock() + + # Simulate the click event + widget.on_issue_nia_click() + + # Verify that the view model method was called with the correct arguments + widget._view_model.issue_nia_asset_view_model.on_issue_click.assert_called_once_with( + 'TTK', 'NIA', '100', + ) + + +def test_handle_button_enabled(issue_nia_widget: IssueNIAWidget, qtbot): + """Test the handle_button_enabled method.""" + widget = issue_nia_widget + + # Mock the input fields + widget.short_identifier_input = MagicMock() + widget.amount_input = MagicMock() + widget.asset_name_input = MagicMock() + widget.issue_nia_btn = MagicMock() + + # Case when all fields are filled + widget.short_identifier_input.text.return_value = 'TTK' + widget.amount_input.text.return_value = '100' + widget.asset_name_input.text.return_value = 'NIA' + + widget.handle_button_enabled() + widget.issue_nia_btn.setDisabled.assert_called_once_with(False) + + # Case when one of the fields is empty + widget.short_identifier_input.text.return_value = '' + + widget.handle_button_enabled() + widget.issue_nia_btn.setDisabled.assert_called_with(True) + + +def test_asset_issued(issue_nia_widget: IssueNIAWidget, qtbot): + """Test the asset_issued method.""" + widget = issue_nia_widget + + # Mock the view model's navigation + widget._view_model.page_navigation.show_success_page = MagicMock() + widget._view_model.page_navigation.fungibles_asset_page = MagicMock() + + # Simulate asset issuance + asset_name = 'NIA' + widget.asset_issued(asset_name) + + # Verify that the success page is shown with correct parameters + widget._view_model.page_navigation.show_success_page.assert_called_once() + + params = widget._view_model.page_navigation.show_success_page.call_args[0][0] + assert_success_page_called(widget, asset_name) + assert params.callback == widget._view_model.page_navigation.fungibles_asset_page + + +def test_update_loading_state_true(issue_nia_widget: IssueNIAWidget): + """Test the update_loading_state method when is_loading is True.""" + + issue_nia_widget.render_timer = MagicMock() + issue_nia_widget.issue_nia_btn = MagicMock() + issue_nia_widget.nia_close_btn = MagicMock() + + # Call the method with is_loading=True + issue_nia_widget.update_loading_state(True) + + # Assert that the render_timer starts + issue_nia_widget.render_timer.start.assert_called_once() + + # Assert that the issue_nia_btn starts loading + issue_nia_widget.issue_nia_btn.start_loading.assert_called_once() + + # Assert that the nia_close_btn is disabled + issue_nia_widget.nia_close_btn.setDisabled.assert_called_once_with( + True, + ) + + +def test_update_loading_state_false(issue_nia_widget: IssueNIAWidget): + """Test the update_loading_state method when is_loading is False.""" + + issue_nia_widget.render_timer = MagicMock() + issue_nia_widget.issue_nia_btn = MagicMock() + issue_nia_widget.nia_close_btn = MagicMock() + + # Call the method with is_loading=False + issue_nia_widget.update_loading_state(False) + + # Assert that the render_timer stops + issue_nia_widget.render_timer.stop.assert_called_once() + + # Assert that the issue_nia_btn stops loading + issue_nia_widget.issue_nia_btn.stop_loading.assert_called_once() + + # Assert that the nia_close_btn is enabled + issue_nia_widget.nia_close_btn.setDisabled.assert_called_once_with( + False, + ) diff --git a/unit_tests/tests/ui_tests/ui_issue_rgb20_test.py b/unit_tests/tests/ui_tests/ui_issue_rgb20_test.py deleted file mode 100644 index f5e76320..00000000 --- a/unit_tests/tests/ui_tests/ui_issue_rgb20_test.py +++ /dev/null @@ -1,155 +0,0 @@ -"""Unit test for Issue RGB20 UI.""" -# Disable the redefined-outer-name warning as -# it's normal to pass mocked objects in test functions -# pylint: disable=redefined-outer-name,unused-argument,protected-access -from __future__ import annotations - -from unittest.mock import MagicMock - -import pytest - -from src.viewmodels.main_view_model import MainViewModel -from src.views.ui_issue_rgb20 import IssueRGB20Widget -from unit_tests.tests.ui_tests.ui_helper_test.issue_asset_helper_test import assert_success_page_called - - -@pytest.fixture -def issue_rgb20_page_navigation(): - """Fixture to create a mocked page navigation object.""" - mock_navigation = MagicMock() - return mock_navigation - - -@pytest.fixture -def mock_issue_rgb20_view_model(issue_rgb20_page_navigation: MagicMock): - """Fixture to create a MainViewModel instance with mocked page navigation.""" - return MainViewModel(issue_rgb20_page_navigation) - - -@pytest.fixture -def issue_rgb20_widget(mock_issue_rgb20_view_model: MainViewModel): - """Fixture to create a IssueRGB20Widget instance.""" - return IssueRGB20Widget(mock_issue_rgb20_view_model) - - -def test_retranslate_ui(issue_rgb20_widget: IssueRGB20Widget): - """Test that the UI strings are correctly translated.""" - issue_rgb20_widget.retranslate_ui() - - assert issue_rgb20_widget.asset_ticker_label.text() == 'asset_ticker' - assert issue_rgb20_widget.asset_name_label.text() == 'asset_name' - - -def test_on_issue_rgb20_click(issue_rgb20_widget: IssueRGB20Widget, qtbot): - """Test the on_issue_rgb20_click method.""" - widget = issue_rgb20_widget - - # Mock the input fields - widget.short_identifier_input = MagicMock() - widget.short_identifier_input.text.return_value = 'TTK' - - widget.asset_name_input = MagicMock() - widget.asset_name_input.text.return_value = 'RGB20' - - widget.amount_input = MagicMock() - widget.amount_input.text.return_value = '100' - - # Mock the view model method - widget._view_model.issue_rgb20_asset_view_model.on_issue_click = MagicMock() - - # Simulate the click event - widget.on_issue_rgb20_click() - - # Verify that the view model method was called with the correct arguments - widget._view_model.issue_rgb20_asset_view_model.on_issue_click.assert_called_once_with( - 'TTK', 'RGB20', '100', - ) - - -def test_handle_button_enabled(issue_rgb20_widget: IssueRGB20Widget, qtbot): - """Test the handle_button_enabled method.""" - widget = issue_rgb20_widget - - # Mock the input fields - widget.short_identifier_input = MagicMock() - widget.amount_input = MagicMock() - widget.asset_name_input = MagicMock() - widget.issue_rgb20_btn = MagicMock() - - # Case when all fields are filled - widget.short_identifier_input.text.return_value = 'TTK' - widget.amount_input.text.return_value = '100' - widget.asset_name_input.text.return_value = 'RGB20' - - widget.handle_button_enabled() - widget.issue_rgb20_btn.setDisabled.assert_called_once_with(False) - - # Case when one of the fields is empty - widget.short_identifier_input.text.return_value = '' - - widget.handle_button_enabled() - widget.issue_rgb20_btn.setDisabled.assert_called_with(True) - - -def test_asset_issued(issue_rgb20_widget: IssueRGB20Widget, qtbot): - """Test the asset_issued method.""" - widget = issue_rgb20_widget - - # Mock the view model's navigation - widget._view_model.page_navigation.show_success_page = MagicMock() - widget._view_model.page_navigation.fungibles_asset_page = MagicMock() - - # Simulate asset issuance - asset_name = 'RGB20' - widget.asset_issued(asset_name) - - # Verify that the success page is shown with correct parameters - widget._view_model.page_navigation.show_success_page.assert_called_once() - - params = widget._view_model.page_navigation.show_success_page.call_args[0][0] - assert_success_page_called(widget, asset_name) - assert params.callback == widget._view_model.page_navigation.fungibles_asset_page - - -def test_update_loading_state_true(issue_rgb20_widget: IssueRGB20Widget): - """Test the update_loading_state method when is_loading is True.""" - - issue_rgb20_widget.render_timer = MagicMock() - issue_rgb20_widget.issue_rgb20_btn = MagicMock() - issue_rgb20_widget.rgb_20_close_btn = MagicMock() - - # Call the method with is_loading=True - issue_rgb20_widget.update_loading_state(True) - - # Assert that the render_timer starts - issue_rgb20_widget.render_timer.start.assert_called_once() - - # Assert that the issue_rgb20_btn starts loading - issue_rgb20_widget.issue_rgb20_btn.start_loading.assert_called_once() - - # Assert that the rgb_20_close_btn is disabled - issue_rgb20_widget.rgb_20_close_btn.setDisabled.assert_called_once_with( - True, - ) - - -def test_update_loading_state_false(issue_rgb20_widget: IssueRGB20Widget): - """Test the update_loading_state method when is_loading is False.""" - - issue_rgb20_widget.render_timer = MagicMock() - issue_rgb20_widget.issue_rgb20_btn = MagicMock() - issue_rgb20_widget.rgb_20_close_btn = MagicMock() - - # Call the method with is_loading=False - issue_rgb20_widget.update_loading_state(False) - - # Assert that the render_timer stops - issue_rgb20_widget.render_timer.stop.assert_called_once() - - # Assert that the issue_rgb20_btn stops loading - issue_rgb20_widget.issue_rgb20_btn.stop_loading.assert_called_once() - - # Assert that the rgb_20_close_btn is enabled - issue_rgb20_widget.rgb_20_close_btn.setDisabled.assert_called_once_with( - False, - ) diff --git a/unit_tests/tests/ui_tests/ui_receive_rgb_asset_test.py b/unit_tests/tests/ui_tests/ui_receive_rgb_asset_test.py index e86f5d61..54346ec2 100644 --- a/unit_tests/tests/ui_tests/ui_receive_rgb_asset_test.py +++ b/unit_tests/tests/ui_tests/ui_receive_rgb_asset_test.py @@ -24,7 +24,7 @@ def receive_rgb_asset_widget(qtbot): """Fixture to create and return an instance of ReceiveRGBAssetWidget.""" mock_navigation = MagicMock() mock_view_model = MagicMock(MainViewModel(mock_navigation)) - asset_data = AssetDataModel(asset_type='RGB25', asset_id='test_asset_id') + asset_data = AssetDataModel(asset_type='CFA', asset_id='test_asset_id') widget = ReceiveRGBAssetWidget(mock_view_model, asset_data) qtbot.addWidget(widget) return widget @@ -32,11 +32,11 @@ def receive_rgb_asset_widget(qtbot): def test_generate_invoice(receive_rgb_asset_widget: ReceiveRGBAssetWidget): """Test that generate_invoice calls get_rgb_invoice for specific asset types.""" - receive_rgb_asset_widget._view_model.receive_rgb25_view_model.get_rgb_invoice = MagicMock() + receive_rgb_asset_widget._view_model.receive_cfa_view_model.get_rgb_invoice = MagicMock() receive_rgb_asset_widget.generate_invoice() - receive_rgb_asset_widget._view_model.receive_rgb25_view_model.get_rgb_invoice.assert_called_once_with( + receive_rgb_asset_widget._view_model.receive_cfa_view_model.get_rgb_invoice.assert_called_once_with( 1, 'test_asset_id', ) @@ -54,10 +54,10 @@ def test_setup_ui_connection(receive_rgb_asset_widget: ReceiveRGBAssetWidget): receive_rgb_asset_widget.receive_rgb_asset_page.copy_button.clicked.emit() # Verify UI connections are set up - receive_rgb_asset_widget._view_model.receive_rgb25_view_model.address.connect.assert_called_once() + receive_rgb_asset_widget._view_model.receive_cfa_view_model.address.connect.assert_called_once() receive_rgb_asset_widget._view_model.ln_offchain_view_model.invoice_get_event.connect.assert_called() - receive_rgb_asset_widget._view_model.receive_rgb25_view_model.message.connect.assert_called() - receive_rgb_asset_widget._view_model.receive_rgb25_view_model.hide_loading.connect.assert_called() + receive_rgb_asset_widget._view_model.receive_cfa_view_model.message.connect.assert_called() + receive_rgb_asset_widget._view_model.receive_cfa_view_model.hide_loading.connect.assert_called() # Verify address label text is set correctly assert receive_rgb_asset_widget.receive_rgb_asset_page.address_label.text() == QCoreApplication.translate( @@ -80,22 +80,22 @@ def test_close_button_navigation(receive_rgb_asset_widget): # Mock ToastManager for error messages with patch.object(ToastManager, 'error') as mock_error: - # Test case 1: Navigation for AssetType.RGB25 - receive_rgb_asset_widget.close_page_navigation = AssetType.RGB25.value + # Test case 1: Navigation for AssetType.CFA + receive_rgb_asset_widget.close_page_navigation = AssetType.CFA.value receive_rgb_asset_widget.close_button_navigation() receive_rgb_asset_widget._view_model.page_navigation.collectibles_asset_page.assert_called_once() receive_rgb_asset_widget._view_model.page_navigation.collectibles_asset_page.reset_mock() - # Test case 2: Navigation for AssetType.RGB20 - receive_rgb_asset_widget.close_page_navigation = AssetType.RGB20.value + # Test case 2: Navigation for AssetType.NIA + receive_rgb_asset_widget.close_page_navigation = AssetType.NIA.value receive_rgb_asset_widget.close_button_navigation() receive_rgb_asset_widget._view_model.page_navigation.fungibles_asset_page.assert_called_once() receive_rgb_asset_widget._view_model.page_navigation.fungibles_asset_page.reset_mock() - # Test case 3: Specific originating page (e.g., 'RGB20') + # Test case 3: Specific originating page (e.g., 'NIA') # Simulate no specific asset navigation receive_rgb_asset_widget.close_page_navigation = None - receive_rgb_asset_widget.originating_page = 'RGB20' + receive_rgb_asset_widget.originating_page = 'NIA' receive_rgb_asset_widget.close_button_navigation() receive_rgb_asset_widget._view_model.page_navigation.fungibles_asset_page.assert_called_once() receive_rgb_asset_widget._view_model.page_navigation.fungibles_asset_page.reset_mock() diff --git a/unit_tests/tests/ui_tests/ui_rgb_asset_detail_test.py b/unit_tests/tests/ui_tests/ui_rgb_asset_detail_test.py index 158a2357..58ab2037 100644 --- a/unit_tests/tests/ui_tests/ui_rgb_asset_detail_test.py +++ b/unit_tests/tests/ui_tests/ui_rgb_asset_detail_test.py @@ -24,6 +24,7 @@ from src.model.selection_page_model import AssetDataModel from src.model.transaction_detail_page_model import TransactionDetailPageModel from src.utils.constant import IRIS_WALLET_TRANSLATIONS_CONTEXT +from src.utils.rgb_asset_helpers import set_asset_image from src.viewmodels.main_view_model import MainViewModel from src.views.components.transaction_detail_frame import TransactionDetailFrame from src.views.ui_rgb_asset_detail import RGBAssetDetailWidget @@ -44,7 +45,7 @@ def rgb_asset_detail_widget(qtbot): # Mock the params as an instance of RgbAssetPageLoadModel mock_params = MagicMock() - mock_params.asset_type = 'RGB20' # Set the asset_type as needed + mock_params.asset_type = 'NIA' # Set the asset_type as needed widget = RGBAssetDetailWidget(view_model, mock_params) qtbot.addWidget(widget) @@ -59,76 +60,16 @@ def test_retranslate_ui(rgb_asset_detail_widget: RGBAssetDetailWidget): assert rgb_asset_detail_widget.transactions_label.text() == 'transfers' -def test_valid_hex_string(rgb_asset_detail_widget: RGBAssetDetailWidget): - """Test with valid hex strings.""" - valid_hex_strings = [ - '00', # simple hex - '0a1b2c3d4e5f', # longer valid hex - 'AABBCCDDEE', # uppercase hex - '1234567890abcdef', # mixed lower and uppercase - ] - for hex_string in valid_hex_strings: - assert rgb_asset_detail_widget.is_hex_string(hex_string) is True - - -def test_invalid_hex_string(rgb_asset_detail_widget: RGBAssetDetailWidget): - """Test with invalid hex strings.""" - invalid_hex_strings = [ - '00G1', # contains non-hex character 'G' - '123z', # contains non-hex character 'z' - '12345', # odd length - '0x1234', # prefixed with '0x' - ' ', # empty or space character - ] - for hex_string in invalid_hex_strings: - assert rgb_asset_detail_widget.is_hex_string(hex_string) is False - - -def test_empty_string(rgb_asset_detail_widget: RGBAssetDetailWidget): - """Test with an empty string.""" - assert rgb_asset_detail_widget.is_hex_string('') is False - - -def test_odd_length_string(rgb_asset_detail_widget: RGBAssetDetailWidget): - """Test with a string of odd length.""" - odd_length_hex_strings = [ - '1', # single character - '123', # three characters - '12345', # five characters - ] - for hex_string in odd_length_hex_strings: - assert rgb_asset_detail_widget.is_hex_string(hex_string) is False - - -def test_is_path(rgb_asset_detail_widget: RGBAssetDetailWidget): - """Test the is_path method with various file paths.""" - - # Test valid Unix-like paths - assert rgb_asset_detail_widget.is_path('/path/to/file') is True - assert rgb_asset_detail_widget.is_path('/usr/local/bin/') is True - assert rgb_asset_detail_widget.is_path('/home/user/doc-1.txt') is True - - # Test invalid paths - assert rgb_asset_detail_widget.is_path( - 'invalid/path', - ) is False # No leading slash - assert rgb_asset_detail_widget.is_path(123) is False # Non-string input - assert rgb_asset_detail_widget.is_path('') is False # Empty string - assert rgb_asset_detail_widget.is_path( - 'C:\\Windows\\Path', - ) is False # Windows path format - - -def test_handle_page_navigation_rgb_20(rgb_asset_detail_widget: RGBAssetDetailWidget): - """Test page navigation handling when asset type is RGB20.""" - rgb_asset_detail_widget.asset_type = AssetType.RGB20.value +def test_handle_page_navigation_nia(rgb_asset_detail_widget: RGBAssetDetailWidget): + """Test page navigation handling when asset type is NIA.""" + rgb_asset_detail_widget.asset_type = AssetType.NIA.value rgb_asset_detail_widget.handle_page_navigation() rgb_asset_detail_widget._view_model.page_navigation.fungibles_asset_page.assert_called_once() -def test_handle_page_navigation_rgb_25(rgb_asset_detail_widget: RGBAssetDetailWidget): - """Test page navigation handling when asset type is RGB25.""" - rgb_asset_detail_widget.asset_type = AssetType.RGB25.value +def test_handle_page_navigation_cfa(rgb_asset_detail_widget: RGBAssetDetailWidget): + """Test page navigation handling when asset type is CFA.""" + rgb_asset_detail_widget.asset_type = AssetType.CFA.value rgb_asset_detail_widget.handle_page_navigation() rgb_asset_detail_widget._view_model.page_navigation.collectibles_asset_page.assert_called_once() @@ -140,7 +81,7 @@ def test_set_transaction_detail_frame(rgb_asset_detail_widget: RGBAssetDetailWid asset_id = 'test_asset_id' asset_name = 'Test Asset' image_path = asset_image_path - asset_type = AssetType.RGB20.value + asset_type = AssetType.NIA.value # Mock the transaction details mock_transaction = MagicMock() @@ -162,7 +103,7 @@ def test_set_transaction_detail_frame(rgb_asset_detail_widget: RGBAssetDetailWid mock_transactions.off_chain_transfers = [mock_transaction] mock_transactions.transfers = [mock_transaction] - rgb_asset_detail_widget._view_model.rgb25_view_model.txn_list = mock_transactions + rgb_asset_detail_widget._view_model.cfa_view_model.txn_list = mock_transactions # Call the method to test rgb_asset_detail_widget.set_transaction_detail_frame( @@ -212,7 +153,7 @@ def test_set_transaction_detail_frame(rgb_asset_detail_widget: RGBAssetDetailWid mock_transactions.onchain_transfers = [mock_transaction] mock_transactions.off_chain_transfers = [mock_transaction] - rgb_asset_detail_widget._view_model.rgb25_view_model.txn_list = mock_transactions + rgb_asset_detail_widget._view_model.cfa_view_model.txn_list = mock_transactions # Call the method to test rgb_asset_detail_widget.set_transaction_detail_frame( @@ -236,8 +177,8 @@ def test_set_transaction_detail_frame(rgb_asset_detail_widget: RGBAssetDetailWid ) == mock_transaction.amount_status -@patch('src.views.ui_rgb_asset_detail.convert_hex_to_image') -@patch('src.views.ui_rgb_asset_detail.resize_image') +@patch('src.utils.rgb_asset_helpers.convert_hex_to_image') +@patch('src.utils.rgb_asset_helpers.resize_image') def test_set_asset_image(mock_resize_image, mock_convert_hex_to_image, rgb_asset_detail_widget: RGBAssetDetailWidget): """Test setting the asset image with mocked image conversion and resizing.""" @@ -255,7 +196,7 @@ def test_set_asset_image(mock_resize_image, mock_convert_hex_to_image, rgb_asset mock_resize_image.return_value = mock_resized_pixmap # Test with hex string - rgb_asset_detail_widget.set_asset_image(mock_hex_image) + set_asset_image(rgb_asset_detail_widget.label_asset_name, mock_hex_image) # Verify that the convert_hex_to_image was called with the correct hex string mock_convert_hex_to_image.assert_called_once_with(mock_hex_image) @@ -340,7 +281,7 @@ def test_select_receive_transfer_type(rgb_asset_detail_widget: RGBAssetDetailWid # Set up mock data for the test asset_id = 'test_asset_id' - asset_type = AssetType.RGB20.value + asset_type = AssetType.NIA.value rgb_asset_detail_widget.asset_id_detail.setPlainText( asset_id, ) # Mock asset_id in widget @@ -356,9 +297,9 @@ def test_select_receive_transfer_type(rgb_asset_detail_widget: RGBAssetDetailWid rgb_asset_detail_widget, 'navigate_to_selection_page', ) - mock_receive_rgb25 = mocker.patch.object( + mock_receive_cfa = mocker.patch.object( rgb_asset_detail_widget._view_model.page_navigation, - 'receive_rgb25_page', + 'receive_cfa_page', ) # Case 1: Channel is open for the asset (is_channel_open_for_asset returns True) @@ -371,11 +312,11 @@ def test_select_receive_transfer_type(rgb_asset_detail_widget: RGBAssetDetailWid mock_navigate.assert_called_once_with( TransferStatusEnumModel.RECEIVE.value, ) - mock_receive_rgb25.assert_not_called() + mock_receive_cfa.assert_not_called() # Reset the mocks to ensure clean state for the next test case mock_navigate.reset_mock() - mock_receive_rgb25.reset_mock() + mock_receive_cfa.reset_mock() # Case 2: Channel is not open for the asset (is_channel_open_for_asset returns False) mock_is_channel_open.return_value = False @@ -384,7 +325,7 @@ def test_select_receive_transfer_type(rgb_asset_detail_widget: RGBAssetDetailWid rgb_asset_detail_widget.select_receive_transfer_type() # Assertions for when the channel is not open - mock_receive_rgb25.assert_called_once_with( + mock_receive_cfa.assert_called_once_with( params=AssetDataModel( asset_type=asset_type, asset_id=asset_id, @@ -413,9 +354,9 @@ def test_select_send_transfer_type(rgb_asset_detail_widget: RGBAssetDetailWidget rgb_asset_detail_widget, 'navigate_to_selection_page', ) - mock_send_rgb25 = mocker.patch.object( + mock_send_cfa = mocker.patch.object( rgb_asset_detail_widget._view_model.page_navigation, - 'send_rgb25_page', + 'send_cfa_page', ) # Case 1: Channel is open for the asset (is_channel_open_for_asset returns True) @@ -428,11 +369,11 @@ def test_select_send_transfer_type(rgb_asset_detail_widget: RGBAssetDetailWidget mock_navigate.assert_called_once_with( TransferStatusEnumModel.SEND.value, ) - mock_send_rgb25.assert_not_called() + mock_send_cfa.assert_not_called() # Reset the mocks to ensure clean state for the next test case mock_navigate.reset_mock() - mock_send_rgb25.reset_mock() + mock_send_cfa.reset_mock() # Case 2: Channel is not open for the asset (is_channel_open_for_asset returns False) mock_is_channel_open.return_value = False @@ -441,7 +382,7 @@ def test_select_send_transfer_type(rgb_asset_detail_widget: RGBAssetDetailWidget rgb_asset_detail_widget.select_send_transfer_type() # Assertions for when the channel is not open - mock_send_rgb25.assert_called_once() + mock_send_cfa.assert_called_once() mock_navigate.assert_not_called() @@ -450,7 +391,7 @@ def test_refresh_transaction(rgb_asset_detail_widget: RGBAssetDetailWidget): # Mock the render timer and the refresh function rgb_asset_detail_widget.render_timer = MagicMock() - rgb_asset_detail_widget._view_model.rgb25_view_model.on_refresh_click = MagicMock() + rgb_asset_detail_widget._view_model.cfa_view_model.on_refresh_click = MagicMock() # Call the method rgb_asset_detail_widget.refresh_transaction() @@ -459,7 +400,7 @@ def test_refresh_transaction(rgb_asset_detail_widget: RGBAssetDetailWidget): # Verify render_timer.start was called once rgb_asset_detail_widget.render_timer.start.assert_called_once() # Verify on_refresh_click was called once - rgb_asset_detail_widget._view_model.rgb25_view_model.on_refresh_click.assert_called_once() + rgb_asset_detail_widget._view_model.cfa_view_model.on_refresh_click.assert_called_once() def test_handle_asset_frame_click(rgb_asset_detail_widget: RGBAssetDetailWidget): @@ -474,13 +415,13 @@ def test_handle_asset_frame_click(rgb_asset_detail_widget: RGBAssetDetailWidget) ) # Mock the navigation method - rgb_asset_detail_widget._view_model.page_navigation.rgb25_transaction_detail_page = MagicMock() + rgb_asset_detail_widget._view_model.page_navigation.cfa_transaction_detail_page = MagicMock() # Call the method rgb_asset_detail_widget.handle_asset_frame_click(params) # Assertions to check if the navigation method was called with the correct parameters - rgb_asset_detail_widget._view_model.page_navigation.rgb25_transaction_detail_page.assert_called_once_with( + rgb_asset_detail_widget._view_model.page_navigation.cfa_transaction_detail_page.assert_called_once_with( params, ) @@ -720,9 +661,13 @@ def test_handle_fail_transfer(rgb_asset_detail_widget: RGBAssetDetailWidget): # Verify dialog was created with correct message containing tx_id mock_confirmation_dialog.assert_called_with( parent=rgb_asset_detail_widget, - message=f"{QCoreApplication.translate(IRIS_WALLET_TRANSLATIONS_CONTEXT, 'transaction_id', None)}: { + message=f"""{ + QCoreApplication.translate( + IRIS_WALLET_TRANSLATIONS_CONTEXT, 'transaction_id', None, + ) + }: { tx_id - }\n\n {QCoreApplication.translate(IRIS_WALLET_TRANSLATIONS_CONTEXT, 'cancel_transfer', None)}", + }\n\n {QCoreApplication.translate(IRIS_WALLET_TRANSLATIONS_CONTEXT, 'cancel_transfer', None)}""", ) # Reset mock for next test @@ -768,7 +713,7 @@ def test_confirm_fail_transfer(rgb_asset_detail_widget: RGBAssetDetailWidget): rgb_asset_detail_widget._confirm_fail_transfer(idx) # Verify the view model method was called with correct index - rgb_asset_detail_widget._view_model.rgb25_view_model.on_fail_transfer.assert_called_once_with( + rgb_asset_detail_widget._view_model.cfa_view_model.on_fail_transfer.assert_called_once_with( idx, ) diff --git a/unit_tests/tests/ui_tests/ui_rgb_asset_transaction_detail_test.py b/unit_tests/tests/ui_tests/ui_rgb_asset_transaction_detail_test.py index c0eb977a..34194afb 100644 --- a/unit_tests/tests/ui_tests/ui_rgb_asset_transaction_detail_test.py +++ b/unit_tests/tests/ui_tests/ui_rgb_asset_transaction_detail_test.py @@ -55,7 +55,7 @@ def rgb_asset_transaction_detail_widget(qtbot): params.recipient_id = 'recipient_124' params.receive_utxo = 'utxo_1234' params.change_utxo = 'utxo_4567' - params.asset_type = 'RGB20' + params.asset_type = 'NIA' params.is_off_chain = False # Add the missing attribute # Initialize the widget @@ -222,18 +222,18 @@ def test_handle_close(rgb_asset_transaction_detail_widget: RGBAssetTransactionDe rgb_asset_transaction_detail_widget.params.asset_type = 'Test Type' # Mock the view model methods (signal and navigation) - rgb_asset_transaction_detail_widget._view_model.rgb25_view_model.asset_info = MagicMock() - rgb_asset_transaction_detail_widget._view_model.page_navigation.rgb25_detail_page = MagicMock() + rgb_asset_transaction_detail_widget._view_model.cfa_view_model.asset_info = MagicMock() + rgb_asset_transaction_detail_widget._view_model.page_navigation.cfa_detail_page = MagicMock() # Call the method to test rgb_asset_transaction_detail_widget.handle_close() # Assert that the signal is emitted with the correct parameters - rgb_asset_transaction_detail_widget._view_model.rgb25_view_model.asset_info.emit.assert_called_once_with( + rgb_asset_transaction_detail_widget._view_model.cfa_view_model.asset_info.emit.assert_called_once_with( '123', 'Test Asset', 'path/to/image', 'Test Type', ) # Assert that the navigation method is called with the correct argument - rgb_asset_transaction_detail_widget._view_model.page_navigation.rgb25_detail_page.assert_called_once_with( + rgb_asset_transaction_detail_widget._view_model.page_navigation.cfa_detail_page.assert_called_once_with( RgbAssetPageLoadModel(asset_type='Test Type'), ) diff --git a/unit_tests/tests/ui_tests/ui_send_ln_invoice_test.py b/unit_tests/tests/ui_tests/ui_send_ln_invoice_test.py index 6916f3aa..1d676396 100644 --- a/unit_tests/tests/ui_tests/ui_send_ln_invoice_test.py +++ b/unit_tests/tests/ui_tests/ui_send_ln_invoice_test.py @@ -20,7 +20,7 @@ def send_ln_invoice_widget(qtbot): """Fixture to create and return an instance of SendLnInvoiceWidget.""" mock_navigation = MagicMock() view_model = MagicMock(MainViewModel(mock_navigation)) - asset_type = 'RGB20' + asset_type = 'NIA' widget = SendLnInvoiceWidget(view_model, asset_type) qtbot.addWidget(widget) return widget @@ -30,7 +30,6 @@ def test_show_invoice_detail(send_ln_invoice_widget: SendLnInvoiceWidget): """Test the show_invoice_detail method with a valid invoice detail.""" invoice_detail_mock = MagicMock() invoice_detail_mock.recipient_id = 'utxob:2PoDFyk-8aegNHZE4-inHHn4nWz-rNtAX3MWv-sTiVPQYrF-ed2bXM' - invoice_detail_mock.asset_iface = 'RGB20' invoice_detail_mock.asset_id = 'rgb:2eVw8uw-8G88LQ2tQ-kexM12SoD-nCX8DmQrw-yLMu6JDfK-xx1SCfc' invoice_detail_mock.amount = 69 invoice_detail_mock.network = 'Regtest' @@ -373,15 +372,15 @@ def test_send_asset(send_ln_invoice_widget): def test_on_success_sent_navigation_collectibles(send_ln_invoice_widget): - """Test the on_success_sent_navigation method when asset type is RGB25 (collectibles).""" + """Test the on_success_sent_navigation method when asset type is CFA (collectibles).""" # Mocking the widget's properties and methods send_ln_invoice_widget._view_model = MagicMock() send_ln_invoice_widget._view_model.page_navigation.collectibles_asset_page = MagicMock() send_ln_invoice_widget._view_model.page_navigation.fungibles_asset_page = MagicMock() - # Set the asset type to RGB25 (collectibles) - send_ln_invoice_widget.asset_type = AssetType.RGB25.value + # Set the asset type to CFA (collectibles) + send_ln_invoice_widget.asset_type = AssetType.CFA.value # Call the method send_ln_invoice_widget.on_success_sent_navigation() @@ -394,14 +393,14 @@ def test_on_success_sent_navigation_collectibles(send_ln_invoice_widget): def test_on_success_sent_navigation_fungibles(send_ln_invoice_widget): - """Test the on_success_sent_navigation method when asset type is not RGB25 (fungibles).""" + """Test the on_success_sent_navigation method when asset type is not CFA (fungibles).""" # Mocking the widget's properties and methods send_ln_invoice_widget._view_model = MagicMock() send_ln_invoice_widget._view_model.page_navigation.collectibles_asset_page = MagicMock() send_ln_invoice_widget._view_model.page_navigation.fungibles_asset_page = MagicMock() - # Set the asset type to a non-RGB25 value (fungibles) + # Set the asset type to a non-CFA value (fungibles) send_ln_invoice_widget.asset_type = 'some_other_asset_type' # Call the method @@ -453,15 +452,15 @@ def test_update_loading_state_not_loading(send_ln_invoice_widget): def test_on_click_close_button_collectibles(send_ln_invoice_widget): - """Test the on_click_close_button method when asset type is RGB25 (collectibles).""" + """Test the on_click_close_button method when asset type is CFA (collectibles).""" # Mocking the widget's properties and methods send_ln_invoice_widget._view_model = MagicMock() send_ln_invoice_widget._view_model.page_navigation.collectibles_asset_page = MagicMock() send_ln_invoice_widget._view_model.page_navigation.fungibles_asset_page = MagicMock() - # Set the asset type to RGB25 (collectibles) - send_ln_invoice_widget.asset_type = AssetType.RGB25.value + # Set the asset type to CFA (collectibles) + send_ln_invoice_widget.asset_type = AssetType.CFA.value # Call the method send_ln_invoice_widget.on_click_close_button() @@ -474,14 +473,14 @@ def test_on_click_close_button_collectibles(send_ln_invoice_widget): def test_on_click_close_button_fungibles(send_ln_invoice_widget): - """Test the on_click_close_button method when asset type is not RGB25 (fungibles).""" + """Test the on_click_close_button method when asset type is not CFA (fungibles).""" # Mocking the widget's properties and methods send_ln_invoice_widget._view_model = MagicMock() send_ln_invoice_widget._view_model.page_navigation.collectibles_asset_page = MagicMock() send_ln_invoice_widget._view_model.page_navigation.fungibles_asset_page = MagicMock() - # Set the asset type to a non-RGB25 value (fungibles) + # Set the asset type to a non-CFA value (fungibles) send_ln_invoice_widget.asset_type = 'some_other_asset_type' # Call the method diff --git a/unit_tests/tests/ui_tests/ui_send_rgb_asset_test.py b/unit_tests/tests/ui_tests/ui_send_rgb_asset_test.py index 2f39c4a2..030b42d3 100644 --- a/unit_tests/tests/ui_tests/ui_send_rgb_asset_test.py +++ b/unit_tests/tests/ui_tests/ui_send_rgb_asset_test.py @@ -36,11 +36,11 @@ def send_rgb_asset_widget(qtbot): asset_balance = Balance(future=100, spendable=50, settled=100) txn_list = MagicMock(ListTransferAssetWithBalanceResponseModel) txn_list.asset_balance = asset_balance - view_model.rgb25_view_model.txn_list = txn_list + view_model.cfa_view_model.txn_list = txn_list - # Mock the attributes of rgb25_view_model - view_model.rgb25_view_model.is_loading = MagicMock() - view_model.rgb25_view_model.stop_loading = MagicMock() + # Mock the attributes of cfa_view_model + view_model.cfa_view_model.is_loading = MagicMock() + view_model.cfa_view_model.stop_loading = MagicMock() widget = SendRGBAssetWidget(view_model) qtbot.addWidget(widget) @@ -108,7 +108,7 @@ def test_set_asset_balance(send_rgb_asset_widget: SendRGBAssetWidget, qtbot): assert send_rgb_asset_widget.send_rgb_asset_page.send_btn.isEnabled() # Test with zero spendable balance - send_rgb_asset_widget._view_model.rgb25_view_model.txn_list.asset_balance.spendable = 0 + send_rgb_asset_widget._view_model.cfa_view_model.txn_list.asset_balance.spendable = 0 send_rgb_asset_widget.set_asset_balance() assert not send_rgb_asset_widget.send_rgb_asset_page.send_btn.isEnabled() @@ -134,13 +134,13 @@ def test_handle_show_message(send_rgb_asset_widget: SendRGBAssetWidget): def test_handle_message(send_rgb_asset_widget: SendRGBAssetWidget): """Test the handle_message method of WelcomeWidget.""" with patch('src.views.ui_send_rgb_asset.ToastManager') as mock_toast_manager: - send_rgb_asset_widget.show_rgb25_message( + send_rgb_asset_widget.show_cfa_message( ToastPreset.ERROR, 'Test Error Message', ) mock_toast_manager.error.assert_called_once_with('Test Error Message') mock_toast_manager.success.assert_not_called() - send_rgb_asset_widget.show_rgb25_message( + send_rgb_asset_widget.show_cfa_message( ToastPreset.SUCCESS, 'Test Success Message', ) mock_toast_manager.error.assert_called_once() @@ -153,21 +153,21 @@ def test_refresh_asset(send_rgb_asset_widget: SendRGBAssetWidget, mocker): """Test the refresh_asset method of the widget.""" # Mock the view model and its methods - mock_rgb25_view_model = MagicMock() + mock_cfa_view_model = MagicMock() mock_view_model = MagicMock() send_rgb_asset_widget._view_model = mock_view_model - send_rgb_asset_widget._view_model.rgb25_view_model = mock_rgb25_view_model + send_rgb_asset_widget._view_model.cfa_view_model = mock_cfa_view_model # Mock the values that should be set on the view model - mock_rgb25_view_model.asset_id = '123' - mock_rgb25_view_model.asset_name = 'Asset Name' - mock_rgb25_view_model.image_path = 'path/to/image' - mock_rgb25_view_model.asset_type = 'type' + mock_cfa_view_model.asset_id = '123' + mock_cfa_view_model.asset_name = 'Asset Name' + mock_cfa_view_model.image_path = 'path/to/image' + mock_cfa_view_model.asset_type = 'type' - # Mock the get_rgb25_asset_detail method - mock_get_rgb25_asset_detail = MagicMock() - mock_rgb25_view_model.get_rgb25_asset_detail = mock_get_rgb25_asset_detail + # Mock the get_cfa_asset_detail method + mock_get_cfa_asset_detail = MagicMock() + mock_cfa_view_model.get_cfa_asset_detail = mock_get_cfa_asset_detail # Call the refresh_asset method send_rgb_asset_widget.refresh_asset() @@ -176,7 +176,7 @@ def test_refresh_asset(send_rgb_asset_widget: SendRGBAssetWidget, mocker): assert send_rgb_asset_widget.loading_performer == 'REFRESH_BUTTON' # Verify that on_refresh_click was called - mock_rgb25_view_model.on_refresh_click.assert_called_once() + mock_cfa_view_model.on_refresh_click.assert_called_once() # Verify that the asset details were correctly assigned assert send_rgb_asset_widget.asset_id == '123' @@ -184,8 +184,8 @@ def test_refresh_asset(send_rgb_asset_widget: SendRGBAssetWidget, mocker): assert send_rgb_asset_widget.image_path == 'path/to/image' assert send_rgb_asset_widget.asset_type == 'type' - # Verify that get_rgb25_asset_detail was called with the correct arguments - mock_rgb25_view_model.get_rgb25_asset_detail.assert_called_once_with( + # Verify that get_cfa_asset_detail was called with the correct arguments + mock_cfa_view_model.get_cfa_asset_detail.assert_called_once_with( asset_id='123', asset_name='Asset Name', image_path='path/to/image', @@ -196,11 +196,11 @@ def test_refresh_asset(send_rgb_asset_widget: SendRGBAssetWidget, mocker): def test_set_originating_page(send_rgb_asset_widget: SendRGBAssetWidget): """Test the set_originating_page method of the widget.""" - # Test when asset_type is 'RGB20' - send_rgb_asset_widget.set_originating_page('RGB20') + # Test when asset_type is 'NIA' + send_rgb_asset_widget.set_originating_page('NIA') - # Verify the asset_type is set to 'RGB20' - assert send_rgb_asset_widget.asset_type == 'RGB20' + # Verify the asset_type is set to 'NIA' + assert send_rgb_asset_widget.asset_type == 'NIA' def test_rgb_asset_page_navigation(send_rgb_asset_widget: SendRGBAssetWidget, mocker): @@ -214,8 +214,8 @@ def test_rgb_asset_page_navigation(send_rgb_asset_widget: SendRGBAssetWidget, mo send_rgb_asset_widget._view_model.page_navigation = mock_page_navigation mock_page_navigation.sidebar.return_value = mock_sidebar - # Test when asset_type is 'RGB20' - send_rgb_asset_widget.asset_type = 'RGB20' + # Test when asset_type is 'NIA' + send_rgb_asset_widget.asset_type = 'NIA' # Call the rgb_asset_page_navigation method send_rgb_asset_widget.rgb_asset_page_navigation() @@ -226,7 +226,7 @@ def test_rgb_asset_page_navigation(send_rgb_asset_widget: SendRGBAssetWidget, mo # Verify that fungibles_asset_page was called mock_page_navigation.fungibles_asset_page.assert_called_once() - # Test when asset_type is not 'RGB20' (e.g., 'OtherType') + # Test when asset_type is not 'NIA' (e.g., 'OtherType') send_rgb_asset_widget.asset_type = 'OtherType' # Call the rgb_asset_page_navigation method again @@ -270,7 +270,7 @@ def test_send_rgb_asset_button_success(send_rgb_asset_widget: SendRGBAssetWidget # Mock the on_send_click method mock_on_send_click = MagicMock() - send_rgb_asset_widget._view_model.rgb25_view_model.on_send_click = mock_on_send_click + send_rgb_asset_widget._view_model.cfa_view_model.on_send_click = mock_on_send_click # Mock ToastManager to prevent actual toast displays mock_toast_manager = MagicMock() @@ -352,7 +352,7 @@ def test_send_rgb_asset_button_send_error(send_rgb_asset_widget: SendRGBAssetWid # Mock on_send_click to raise CommonException mock_error = CommonException('Send failed') mock_on_send_click = MagicMock(side_effect=mock_error) - send_rgb_asset_widget._view_model.rgb25_view_model.on_send_click = mock_on_send_click + send_rgb_asset_widget._view_model.cfa_view_model.on_send_click = mock_on_send_click # Use patch to mock ToastManager with patch('src.views.ui_send_rgb_asset.ToastManager') as mock_toast_manager: diff --git a/unit_tests/tests/ui_tests/ui_settings_test.py b/unit_tests/tests/ui_tests/ui_settings_test.py index 719c6e0c..dca1fa41 100644 --- a/unit_tests/tests/ui_tests/ui_settings_test.py +++ b/unit_tests/tests/ui_tests/ui_settings_test.py @@ -9,22 +9,10 @@ import pytest from PySide6.QtGui import QIntValidator -from PySide6.QtWidgets import QDialog from src.model.enums.enums_model import NetworkEnumModel -from src.utils.constant import BITCOIND_RPC_HOST_MAINNET -from src.utils.constant import BITCOIND_RPC_HOST_REGTEST -from src.utils.constant import BITCOIND_RPC_HOST_TESTNET -from src.utils.constant import BITCOIND_RPC_PORT_MAINNET -from src.utils.constant import BITCOIND_RPC_PORT_REGTEST -from src.utils.constant import BITCOIND_RPC_PORT_TESTNET -from src.utils.constant import INDEXER_URL_MAINNET -from src.utils.constant import INDEXER_URL_REGTEST -from src.utils.constant import INDEXER_URL_TESTNET -from src.utils.constant import PROXY_ENDPOINT_MAINNET -from src.utils.constant import PROXY_ENDPOINT_REGTEST -from src.utils.constant import PROXY_ENDPOINT_TESTNET from src.viewmodels.main_view_model import MainViewModel +from src.views.components.settings_helpers import check_keyring_state_for_password from src.views.ui_settings import SettingsWidget @@ -214,12 +202,17 @@ def test_set_expiry_time(setting_widget): ) -def test_set_indexer_url(setting_widget): +def test_set_indexer_url(setting_widget, mocker): """Test setting indexer URL.""" - # Mock components and check_keyring_state + # Mock components setting_widget.set_indexer_url_frame = MagicMock() setting_widget.set_indexer_url_frame.input_value.text.return_value = 'http://example.com' - setting_widget._check_keyring_state = MagicMock(return_value='password123') + + # Mock check_keyring_state_for_password + mocker.patch( + 'src.views.ui_settings.check_keyring_state_for_password', + return_value='password123', + ) # Mock setting view model setting_widget._view_model.setting_view_model = MagicMock() @@ -233,12 +226,17 @@ def test_set_indexer_url(setting_widget): ) -def test_set_proxy_endpoint(setting_widget): +def test_set_proxy_endpoint(setting_widget, mocker): """Test setting proxy endpoint.""" - # Mock components and check_keyring_state + # Mock components setting_widget.set_proxy_endpoint_frame = MagicMock() setting_widget.set_proxy_endpoint_frame.input_value.text.return_value = 'http://proxy.com' - setting_widget._check_keyring_state = MagicMock(return_value='password123') + + # Mock check_keyring_state_for_password + mocker.patch( + 'src.views.ui_settings.check_keyring_state_for_password', + return_value='password123', + ) # Mock setting view model setting_widget._view_model.setting_view_model = MagicMock() @@ -252,12 +250,17 @@ def test_set_proxy_endpoint(setting_widget): ) -def test_set_bitcoind_host(setting_widget): +def test_set_bitcoind_host(setting_widget, mocker): """Test setting bitcoind host.""" - # Mock components and check_keyring_state + # Mock components setting_widget.set_bitcoind_rpc_host_frame = MagicMock() setting_widget.set_bitcoind_rpc_host_frame.input_value.text.return_value = 'localhost' - setting_widget._check_keyring_state = MagicMock(return_value='password123') + + # Mock check_keyring_state_for_password + mocker.patch( + 'src.views.ui_settings.check_keyring_state_for_password', + return_value='password123', + ) # Mock setting view model setting_widget._view_model.setting_view_model = MagicMock() @@ -271,12 +274,17 @@ def test_set_bitcoind_host(setting_widget): ) -def test_set_bitcoind_port(setting_widget): +def test_set_bitcoind_port(setting_widget, mocker): """Test setting bitcoind port.""" - # Mock components and check_keyring_state + # Mock components setting_widget.set_bitcoind_rpc_port_frame = MagicMock() setting_widget.set_bitcoind_rpc_port_frame.input_value.text.return_value = '8332' - setting_widget._check_keyring_state = MagicMock(return_value='password123') + + # Mock check_keyring_state_for_password + mocker.patch( + 'src.views.ui_settings.check_keyring_state_for_password', + return_value='password123', + ) # Mock setting view model setting_widget._view_model.setting_view_model = MagicMock() @@ -290,12 +298,17 @@ def test_set_bitcoind_port(setting_widget): ) -def test_set_announce_address(setting_widget): +def test_set_announce_address(setting_widget, mocker): """Test setting announce address.""" - # Mock components and check_keyring_state + # Mock components setting_widget.set_announce_address_frame = MagicMock() setting_widget.set_announce_address_frame.input_value.text.return_value = 'example.com' - setting_widget._check_keyring_state = MagicMock(return_value='password123') + + # Mock check_keyring_state_for_password + mocker.patch( + 'src.views.ui_settings.check_keyring_state_for_password', + return_value='password123', + ) # Mock setting view model setting_widget._view_model.setting_view_model = MagicMock() @@ -309,12 +322,17 @@ def test_set_announce_address(setting_widget): ) -def test_set_announce_alias(setting_widget): +def test_set_announce_alias(setting_widget, mocker): """Test setting announce alias.""" - # Mock components and check_keyring_state + # Mock components setting_widget.set_announce_alias_frame = MagicMock() setting_widget.set_announce_alias_frame.input_value.text.return_value = 'my-node' - setting_widget._check_keyring_state = MagicMock(return_value='password123') + + # Mock check_keyring_state_for_password + mocker.patch( + 'src.views.ui_settings.check_keyring_state_for_password', + return_value='password123', + ) # Mock setting view model setting_widget._view_model.setting_view_model = MagicMock() @@ -539,67 +557,6 @@ def test_handle_on_error_empty_message(setting_widget, mocker): mock_toast.error.assert_called_once_with('') -def test_set_frame_content(setting_widget, mocker): - """Test setting frame content with various configurations.""" - # Create mock frame and components - mock_frame = MagicMock() - mock_frame.input_value = MagicMock() - mock_frame.suggestion_desc = MagicMock() - mock_frame.time_unit_combobox = MagicMock() - mock_frame.save_button = MagicMock() - - # Test with float input that's an integer - setting_widget._set_frame_content(mock_frame, 10.0) - mock_frame.input_value.setText.assert_called_with('10') - mock_frame.input_value.setPlaceholderText.assert_called_with('10') - mock_frame.suggestion_desc.hide.assert_called_once() - mock_frame.time_unit_combobox.hide.assert_called_once() - - # Reset mocks - mock_frame.reset_mock() - - # Test with validator - mock_validator = MagicMock() - setting_widget._set_frame_content(mock_frame, 10, validator=mock_validator) - mock_frame.input_value.setValidator.assert_called_with(mock_validator) - - # Reset mocks - mock_frame.reset_mock() - - # Test with time unit combobox - mock_combobox = MagicMock() - setting_widget.expiry_time_unit = 'hours' - mock_combobox.findText.return_value = 1 - setting_widget._set_frame_content( - mock_frame, 10, time_unit_combobox=mock_combobox, - ) - mock_combobox.setCurrentIndex.assert_called_with(1) - - -def test_update_save_button(setting_widget): - """Test updating save button state.""" - # Create mock frame - mock_frame = MagicMock() - mock_frame.input_value.text.return_value = '20' - mock_frame.time_unit_combobox.currentText.return_value = 'hours' - - # Test when input value has changed - setting_widget._update_save_button(mock_frame, '10') - mock_frame.save_button.setDisabled.assert_called_with(False) - - # Test when input value hasn't changed - mock_frame.input_value.text.return_value = '10' - setting_widget._update_save_button(mock_frame, '10') - mock_frame.save_button.setDisabled.assert_called_with(True) - - # Test with time unit change - setting_widget.expiry_time_unit = 'minutes' - setting_widget._update_save_button( - mock_frame, '10', mock_frame.time_unit_combobox, - ) - mock_frame.save_button.setDisabled.assert_called_with(False) - - def test_handle_fee_rate_frame(setting_widget): """Test handling fee rate frame.""" # Mock frame and components @@ -698,83 +655,6 @@ def test_handle_other_frames(setting_widget): frame.input_value.setPlaceholderText.assert_called_with(str(value)) -def test_check_keyring_state_disabled(setting_widget, mocker): - """Test checking keyring state when keyring is disabled.""" - # Mock dependencies - mocker.patch( - 'src.views.ui_settings.SettingRepository.get_keyring_status', return_value=False, - ) - mocker.patch( - 'src.views.ui_settings.SettingRepository.get_wallet_network', - return_value=NetworkEnumModel.MAINNET, - ) - mock_get_value = mocker.patch( - 'src.views.ui_settings.get_value', return_value='test_password', - ) - - # Call method - result = setting_widget._check_keyring_state() - - # Verify password was retrieved from storage - mock_get_value.assert_called_once_with('wallet_password', 'mainnet') - assert result == 'test_password' - - -def test_check_keyring_state_enabled_accepted(setting_widget, mocker): - """Test checking keyring state when keyring is enabled and dialog is accepted.""" - # Mock dependencies - mocker.patch( - 'src.views.ui_settings.SettingRepository.get_keyring_status', return_value=True, - ) - mock_dialog = MagicMock() - mock_dialog.exec.return_value = QDialog.Accepted - mock_dialog.password_input.text.return_value = 'dialog_password' - mock_dialog_class = mocker.patch( - 'src.views.ui_settings.RestoreMnemonicWidget', - return_value=mock_dialog, - ) - - # Call method - result = setting_widget._check_keyring_state() - - # Verify dialog was created with correct parameters - mock_dialog_class.assert_called_once_with( - parent=setting_widget, - view_model=setting_widget._view_model, - origin_page='setting_card', - mnemonic_visibility=False, - ) - - # Verify dialog was configured correctly - mock_dialog.mnemonic_detail_text_label.setText.assert_called_once() - mock_dialog.mnemonic_detail_text_label.setFixedHeight.assert_called_once_with( - 40, - ) - - # Verify password was retrieved from dialog - assert result == 'dialog_password' - - -def test_check_keyring_state_enabled_rejected(setting_widget, mocker): - """Test checking keyring state when keyring is enabled and dialog is rejected.""" - # Mock dependencies - mocker.patch( - 'src.views.ui_settings.SettingRepository.get_keyring_status', return_value=True, - ) - mock_dialog = MagicMock() - mock_dialog.exec.return_value = QDialog.Rejected - mocker.patch( - 'src.views.ui_settings.RestoreMnemonicWidget', - return_value=mock_dialog, - ) - - # Call method - result = setting_widget._check_keyring_state() - - # Verify None is returned when dialog is rejected - assert result is None - - def test_check_keyring_state_invalid(setting_widget, mocker): """Test checking keyring state with invalid keyring status.""" # Mock dependencies @@ -783,7 +663,9 @@ def test_check_keyring_state_invalid(setting_widget, mocker): ) # Call method - result = setting_widget._check_keyring_state() + result = check_keyring_state_for_password( + setting_widget, setting_widget._view_model, + ) # Verify None is returned for invalid keyring status assert result is None @@ -839,71 +721,3 @@ def test_update_loading_state_not_loading(setting_widget): frame = getattr(setting_widget, frame_name) frame.save_button.stop_loading.assert_called_once() assert not frame.save_button.start_loading.called - - -def test_set_endpoint_based_on_network_mainnet(setting_widget, mocker): - """Test setting endpoints for mainnet network.""" - # Mock SettingRepository to return mainnet - mocker.patch( - 'src.views.ui_settings.SettingRepository.get_wallet_network', - return_value=NetworkEnumModel.MAINNET, - ) - - # Call method - setting_widget._set_endpoint_based_on_network() - - # Verify correct endpoints were set - assert setting_widget.indexer_url == INDEXER_URL_MAINNET - assert setting_widget.proxy_endpoint == PROXY_ENDPOINT_MAINNET - assert setting_widget.bitcoind_host == BITCOIND_RPC_HOST_MAINNET - assert setting_widget.bitcoind_port == BITCOIND_RPC_PORT_MAINNET - - -def test_set_endpoint_based_on_network_testnet(setting_widget, mocker): - """Test setting endpoints for testnet network.""" - # Mock SettingRepository to return testnet - mocker.patch( - 'src.views.ui_settings.SettingRepository.get_wallet_network', - return_value=NetworkEnumModel.TESTNET, - ) - - # Call method - setting_widget._set_endpoint_based_on_network() - - # Verify correct endpoints were set - assert setting_widget.indexer_url == INDEXER_URL_TESTNET - assert setting_widget.proxy_endpoint == PROXY_ENDPOINT_TESTNET - assert setting_widget.bitcoind_host == BITCOIND_RPC_HOST_TESTNET - assert setting_widget.bitcoind_port == BITCOIND_RPC_PORT_TESTNET - - -def test_set_endpoint_based_on_network_regtest(setting_widget, mocker): - """Test setting endpoints for regtest network.""" - # Mock SettingRepository to return regtest - mocker.patch( - 'src.views.ui_settings.SettingRepository.get_wallet_network', - return_value=NetworkEnumModel.REGTEST, - ) - - # Call method - setting_widget._set_endpoint_based_on_network() - - # Verify correct endpoints were set - assert setting_widget.indexer_url == INDEXER_URL_REGTEST - assert setting_widget.proxy_endpoint == PROXY_ENDPOINT_REGTEST - assert setting_widget.bitcoind_host == BITCOIND_RPC_HOST_REGTEST - assert setting_widget.bitcoind_port == BITCOIND_RPC_PORT_REGTEST - - -def test_set_endpoint_based_on_network_invalid(setting_widget, mocker): - """Test setting endpoints with invalid network type.""" - # Mock SettingRepository to return invalid network - mocker.patch( - 'src.views.ui_settings.SettingRepository.get_wallet_network', - return_value='invalid_network', - ) - - # Verify ValueError is raised - with pytest.raises(ValueError) as exc_info: - setting_widget._set_endpoint_based_on_network() - assert 'Unsupported network type' in str(exc_info.value) diff --git a/unit_tests/tests/ui_tests/ui_wallet_or_transfer_selection_test.py b/unit_tests/tests/ui_tests/ui_wallet_or_transfer_selection_test.py index cc3a0776..b0457e0e 100644 --- a/unit_tests/tests/ui_tests/ui_wallet_or_transfer_selection_test.py +++ b/unit_tests/tests/ui_tests/ui_wallet_or_transfer_selection_test.py @@ -93,11 +93,11 @@ def test_handle_frame_click_on_chain_receive(wallet_or_transfer_selection_widget # Mock asset_type if necessary # Ensure asset_type is set - wallet_or_transfer_selection_widget.asset_type = AssetType.RGB20.value + wallet_or_transfer_selection_widget.asset_type = AssetType.NIA.value # Mock the methods that should be called - mock_receive_rgb25_page = mocker.patch.object( - wallet_or_transfer_selection_widget._view_model.page_navigation, 'receive_rgb25_page', + mock_receive_cfa_page = mocker.patch.object( + wallet_or_transfer_selection_widget._view_model.page_navigation, 'receive_cfa_page', ) # Call the method with the on-chain ID @@ -106,9 +106,9 @@ def test_handle_frame_click_on_chain_receive(wallet_or_transfer_selection_widget ) # Assertions - mock_receive_rgb25_page.assert_called_once_with( + mock_receive_cfa_page.assert_called_once_with( params=AssetDataModel( - asset_type=AssetType.RGB20.value, # Ensure asset_type is passed as expected + asset_type=AssetType.NIA.value, # Ensure asset_type is passed as expected asset_id='test_asset_id', ), ) @@ -120,8 +120,8 @@ def test_handle_frame_click_on_chain_send(wallet_or_transfer_selection_widget: W wallet_or_transfer_selection_widget._params.callback = TransferStatusEnumModel.SEND.value # Mock the methods that should be called - mock_send_rgb25_page = mocker.patch.object( - wallet_or_transfer_selection_widget._view_model.page_navigation, 'send_rgb25_page', + mock_send_cfa_page = mocker.patch.object( + wallet_or_transfer_selection_widget._view_model.page_navigation, 'send_cfa_page', ) # Call the method with the on-chain ID @@ -130,7 +130,7 @@ def test_handle_frame_click_on_chain_send(wallet_or_transfer_selection_widget: W ) # Assertions - mock_send_rgb25_page.assert_called_once() + mock_send_cfa_page.assert_called_once() def test_handle_frame_click_off_chain_receive(wallet_or_transfer_selection_widget: WalletOrTransferSelectionWidget, mocker): @@ -227,14 +227,14 @@ def test_close_button_navigation(wallet_or_transfer_selection_widget: WalletOrTr mock_back_page_navigation = mocker.Mock() wallet_or_transfer_selection_widget._params.back_page_navigation = mock_back_page_navigation - # Mock the rgb25_view_model object - mock_rgb25_view_model = mocker.patch.object( - wallet_or_transfer_selection_widget._view_model, 'rgb25_view_model', autospec=False, + # Mock the cfa_view_model object + mock_cfa_view_model = mocker.patch.object( + wallet_or_transfer_selection_widget._view_model, 'cfa_view_model', autospec=False, ) # Create a MagicMock for asset_info and mock its emit method mock_asset_info = mocker.MagicMock() - mock_rgb25_view_model.asset_info = mock_asset_info + mock_cfa_view_model.asset_info = mock_asset_info # Set up rgb_asset_page_load_model with test values wallet_or_transfer_selection_widget._params.rgb_asset_page_load_model = mocker.Mock( diff --git a/unit_tests/tests/utils_test/helper_test.py b/unit_tests/tests/utils_test/helper_test.py index 4b014ece..eb3738f4 100644 --- a/unit_tests/tests/utils_test/helper_test.py +++ b/unit_tests/tests/utils_test/helper_test.py @@ -24,7 +24,9 @@ from src.utils.helpers import hash_mnemonic from src.utils.helpers import is_port_available from src.utils.helpers import load_stylesheet +from src.utils.helpers import read_ln_node_commit_id_file from src.utils.helpers import validate_mnemonic +from src.utils.helpers import write_ln_node_commit_id_file @pytest.fixture @@ -255,3 +257,54 @@ def test_handle_generic_error_logs_and_raises(mocker): handle_generic_error(context, Exception('boom'), message) assert str(exc_info.value) == message mock_logger.error.assert_called() + + +@patch('src.utils.helpers.SettingRepository.get_rln_node_commit_id') +@patch('src.utils.helpers.app_paths') +def test_write_ln_node_commit_id_file_writes_and_returns_path_and_name( + mock_app_paths, mock_get_commit_id, +): + """Test the `write_ln_node_commit_id_file` function to ensure it writes the commit ID to a file and returns the path and name.""" + mock_app_paths.backup_folder_path = '/fake/backup' + mock_get_commit_id.return_value = 'commit123' + m = mock_open() + with patch('builtins.open', m): + path, name = write_ln_node_commit_id_file('wallet') + + expected_name = 'wallet.commit' + expected_path = os.path.join('/fake/backup', expected_name) + + assert name == expected_name + assert path == expected_path + m.assert_called_once_with(expected_path, 'w', encoding='utf-8') + handle = m() + handle.write.assert_called_once_with('commit123') + + +@patch('src.utils.helpers.app_paths') +def test_read_ln_node_commit_id_file_returns_content( + mock_app_paths, +): + """Test the `read_ln_node_commit_id_file` function to ensure it returns the content of the commit ID file.""" + mock_app_paths.restore_folder_path = '/fake/restore' + + file_name = 'wallet.commit' + full_path = os.path.join('/fake/restore', file_name) + m = mock_open(read_data='commitXYZ') + with patch('builtins.open', m): + result = read_ln_node_commit_id_file(file_name) + m.assert_called_once_with(full_path, encoding='utf-8') + assert result == 'commitXYZ' + + +@patch('src.utils.helpers.app_paths') +def test_read_ln_node_commit_id_file_returns_unknown_when_missing( + mock_app_paths, +): + """Test the `read_ln_node_commit_id_file` function to ensure it returns 'unknown' when the commit ID file is missing.""" + mock_app_paths.restore_folder_path = '/fake/restore' + m = mock_open() + m.side_effect = FileNotFoundError + with patch('builtins.open', m): + result = read_ln_node_commit_id_file('missing.commit') + assert result == 'unknown' diff --git a/unit_tests/tests/utils_test/page_navigation_test.py b/unit_tests/tests/utils_test/page_navigation_test.py index 43899023..63580ab8 100644 --- a/unit_tests/tests/utils_test/page_navigation_test.py +++ b/unit_tests/tests/utils_test/page_navigation_test.py @@ -130,11 +130,11 @@ def test_enter_wallet_password_page(page_navigation): assert page_navigation.current_stack['name'] == 'EnterWalletPassword' -def test_issue_rgb20_asset_page(page_navigation): - """Test issue_rgb20_asset_page navigation.""" - page_navigation.issue_rgb20_asset_page() +def test_issue_nia_asset_page(page_navigation): + """Test issue_nia_asset_page navigation.""" + page_navigation.issue_nia_asset_page() - assert page_navigation.current_stack['name'] == 'IssueRGB20' + assert page_navigation.current_stack['name'] == 'IssueNIA' def test_bitcoin_page(page_navigation): @@ -144,34 +144,34 @@ def test_bitcoin_page(page_navigation): assert page_navigation.current_stack['name'] == 'Bitcoin' -def test_issue_rgb25_asset_page(page_navigation): - """Test issue_rgb25_asset_page navigation.""" - page_navigation.issue_rgb25_asset_page() +def test_issue_cfa_asset_page(page_navigation): + """Test issue_cfa_asset_page navigation.""" + page_navigation.issue_cfa_asset_page() - assert page_navigation.current_stack['name'] == 'IssueRGB25' + assert page_navigation.current_stack['name'] == 'IssueCFA' -def test_send_rgb25_page(page_navigation): - """Test send_rgb25_page navigation.""" - page_navigation.send_rgb25_page() +def test_send_cfa_page(page_navigation): + """Test send_cfa_page navigation.""" + page_navigation.send_cfa_page() - assert page_navigation.current_stack['name'] == 'SendRGB25' + assert page_navigation.current_stack['name'] == 'SendCFA' -def test_receive_rgb25_page(page_navigation): - """Test receive_rgb25_page navigation.""" +def test_receive_cfa_page(page_navigation): + """Test receive_cfa_page navigation.""" params = MagicMock() - page_navigation.receive_rgb25_page(params) + page_navigation.receive_cfa_page(params) - assert page_navigation.current_stack['name'] == 'ReceiveRGB25' + assert page_navigation.current_stack['name'] == 'ReceiveCFA' -def test_rgb25_detail_page(page_navigation): - """Test rgb25_detail_page navigation.""" +def test_cfa_detail_page(page_navigation): + """Test cfa_detail_page navigation.""" params = MagicMock(spec=RgbAssetPageLoadModel) - page_navigation.rgb25_detail_page(params) + page_navigation.cfa_detail_page(params) - assert page_navigation.current_stack['name'] == 'RGB25Detail' + assert page_navigation.current_stack['name'] == 'CFADetail' def test_send_bitcoin_page(page_navigation): @@ -209,12 +209,12 @@ def test_view_unspent_list_page(page_navigation): assert page_navigation.current_stack['name'] == 'ViewUnspentList' -def test_rgb25_transaction_detail_page(page_navigation): - """Test rgb25_transaction_detail_page navigation.""" +def test_cfa_transaction_detail_page(page_navigation): + """Test cfa_transaction_detail_page navigation.""" params = MagicMock(spec=TransactionDetailPageModel) - page_navigation.rgb25_transaction_detail_page(params) + page_navigation.cfa_transaction_detail_page(params) - assert page_navigation.current_stack['name'] == 'RGB25TransactionDetail' + assert page_navigation.current_stack['name'] == 'CFATransactionDetail' def test_bitcoin_transaction_detail_page(page_navigation): diff --git a/unit_tests/tests/utils_test/render_timer_test.py b/unit_tests/tests/utils_test/render_timer_test.py index 2e2751d4..6570c67c 100644 --- a/unit_tests/tests/utils_test/render_timer_test.py +++ b/unit_tests/tests/utils_test/render_timer_test.py @@ -27,6 +27,8 @@ def render_timer(): def test_singleton_pattern(): """Test that RenderTimer follows the singleton pattern.""" + # Clear the singleton instance to ensure test isolation + RenderTimer._instance = None with patch('src.utils.render_timer.logger'): # Create two instances timer1 = RenderTimer('Task 1') @@ -108,6 +110,8 @@ def test_multiple_start_stop_cycles(render_timer): def test_timer_initialization(): """Test that timer is properly initialized.""" + # Clear the singleton instance to ensure test isolation + RenderTimer._instance = None with patch('src.utils.render_timer.logger'): timer = RenderTimer('Test Task') assert isinstance(timer.timer, QElapsedTimer) @@ -126,6 +130,8 @@ def test_stop_resets_rendering_flag(render_timer): def test_initialization_happens_once(): """Test that initialization only happens once despite multiple instantiations.""" + # Clear the singleton instance to ensure test isolation + RenderTimer._instance = None with patch('src.utils.render_timer.logger'): timer1 = RenderTimer('Task 1') original_timer = timer1.timer diff --git a/unit_tests/tests/utils_test/rgb_asset_helpers_test.py b/unit_tests/tests/utils_test/rgb_asset_helpers_test.py new file mode 100644 index 00000000..9d204404 --- /dev/null +++ b/unit_tests/tests/utils_test/rgb_asset_helpers_test.py @@ -0,0 +1,188 @@ +"""Unit tests for RGB asset helper functions.""" +from __future__ import annotations + +from unittest.mock import MagicMock +from unittest.mock import patch + +from PySide6.QtWidgets import QLabel + +from src.utils.rgb_asset_helpers import handle_img_path +from src.utils.rgb_asset_helpers import is_hex_string +from src.utils.rgb_asset_helpers import is_path +from src.utils.rgb_asset_helpers import set_asset_image + + +def test_valid_hex_string(): + """Test with valid hex strings.""" + valid_hex_strings = [ + '00', # simple hex + '0a1b2c3d4e5f', # longer valid hex + 'AABBCCDDEE', # uppercase hex + '1234567890abcdef', # mixed lower and uppercase + ] + for hex_string in valid_hex_strings: + assert is_hex_string(hex_string) is True + + +def test_invalid_hex_string(): + """Test with invalid hex strings.""" + invalid_hex_strings = [ + '00G1', # contains non-hex character 'G' + '123z', # contains non-hex character 'z' + '12345', # odd length + '0x1234', # prefixed with '0x' + ' ', # empty or space character + ] + for hex_string in invalid_hex_strings: + assert is_hex_string(hex_string) is False + + +def test_empty_string(): + """Test with an empty string.""" + assert is_hex_string('') is False + + +def test_odd_length_string(): + """Test with a string of odd length.""" + odd_length_hex_strings = [ + '1', # single character + '123', # three characters + '12345', # five characters + ] + for hex_string in odd_length_hex_strings: + assert is_hex_string(hex_string) is False + + +def test_is_path_valid(): + """Test the is_path function with valid Unix-like paths.""" + # Test valid Unix-like paths + assert is_path('/path/to/file') is True + assert is_path('/usr/local/bin/') is True + assert is_path('/home/user/doc-1.txt') is True + + +def test_is_path_invalid(): + """Test the is_path function with invalid paths.""" + # Test invalid paths + assert is_path('invalid/path') is False # No leading slash + assert is_path(123) is False # Non-string input + assert is_path('') is False # Empty string + assert is_path('C:\\Windows\\Path') is False # Windows path format + + +@patch('src.utils.rgb_asset_helpers.convert_hex_to_image') +@patch('src.utils.rgb_asset_helpers.resize_image') +def test_set_asset_image_with_hex(mock_resize_image, mock_convert_hex_to_image): + """Test setting asset image from hex string.""" + # Setup + mock_label = MagicMock(spec=QLabel) + mock_pixmap = MagicMock() + mock_resized = MagicMock() + mock_convert_hex_to_image.return_value = mock_pixmap + mock_resize_image.return_value = mock_resized + + # Call with hex string + set_asset_image(mock_label, '1234567890abcdef') + + # Verify + mock_convert_hex_to_image.assert_called_once_with('1234567890abcdef') + mock_resize_image.assert_called_once_with(mock_pixmap, 335, 335) + mock_label.setPixmap.assert_called_once_with(mock_resized) + + +@patch('src.utils.rgb_asset_helpers.resize_image') +def test_set_asset_image_with_path(mock_resize_image): + """Test setting asset image from file path.""" + # Setup + mock_label = MagicMock(spec=QLabel) + mock_resized = MagicMock() + mock_resize_image.return_value = mock_resized + + # Call with file path (not hex string) + set_asset_image(mock_label, '/path/to/image.png') + + # Verify + mock_resize_image.assert_called_once_with('/path/to/image.png', 335, 335) + + +@patch('src.utils.rgb_asset_helpers.set_asset_image') +@patch('src.utils.rgb_asset_helpers.QLabel') +def test_handle_img_path_with_path(mock_qlabel_class, mock_set_asset_image): + """Test handle_img_path with a valid image path.""" + # Setup mocks + mock_widget = MagicMock() + mock_layout = MagicMock() + mock_frame = MagicMock() + mock_label = MagicMock(spec=QLabel) + mock_qlabel_class.return_value = mock_label + image_path = '/path/to/image.png' + + # Call + result = handle_img_path( + mock_widget, + image_path, + mock_layout, + mock_frame, + ) + + # Verify widget configuration + mock_widget.setMinimumSize.assert_called_once() + mock_widget.setFixedWidth.assert_called_once_with(499) + + # Verify label was created + mock_qlabel_class.assert_called_once_with(mock_widget) + assert result == mock_label + + # Verify set_asset_image was called + mock_set_asset_image.assert_called_once_with( + mock_label, image_hex=image_path, + ) + # Setup + mock_widget = MagicMock() + mock_layout = MagicMock() + mock_frame = MagicMock() + existing_label = MagicMock(spec=QLabel) + + # Call with no path + result = handle_img_path( + mock_widget, + '', + mock_layout, + mock_frame, + existing_label, + ) + + # Verify nothing was configured and existing label was returned + assert result == existing_label + mock_widget.setMinimumSize.assert_not_called() + mock_layout.addWidget.assert_not_called() + + +@patch('src.utils.rgb_asset_helpers.set_asset_image') +def test_handle_img_path_with_existing_label(mock_set_asset_image): + """Test handle_img_path with an existing label.""" + # Setup + mock_widget = MagicMock() + mock_layout = MagicMock() + mock_frame = MagicMock() + existing_label = MagicMock(spec=QLabel) + image_path = '/path/to/image.png' + + # Call with existing label + result = handle_img_path( + mock_widget, + image_path, + mock_layout, + mock_frame, + existing_label, + ) + + # Verify existing label was used and configured + assert result == existing_label + existing_label.setMaximumSize.assert_called_once() + existing_label.setStyleSheet.assert_called_once() + + # Verify set_asset_image was called with existing label + mock_set_asset_image.assert_called_once_with( + existing_label, image_hex=image_path, + ) diff --git a/unit_tests/tests/viewmodel_tests/rgb_25_view_model_test.py b/unit_tests/tests/viewmodel_tests/cfa_view_model_test.py similarity index 52% rename from unit_tests/tests/viewmodel_tests/rgb_25_view_model_test.py rename to unit_tests/tests/viewmodel_tests/cfa_view_model_test.py index a73a547a..6b057473 100644 --- a/unit_tests/tests/viewmodel_tests/rgb_25_view_model_test.py +++ b/unit_tests/tests/viewmodel_tests/cfa_view_model_test.py @@ -1,4 +1,4 @@ -"""Unit test for rgb25 view model""" +"""Unit test for cfa view model""" # Disable the redefined-outer-name warning as # it's normal to pass mocked object in tests function # pylint: disable=redefined-outer-name,unused-argument,too-many-statements,protected-access @@ -12,8 +12,10 @@ import pytest from src.model.enums.enums_model import AssetType +from src.model.enums.enums_model import AssignmentEnumModel from src.model.enums.enums_model import TransferStatusEnumModel from src.model.rgb_model import AssetBalanceResponseModel +from src.model.rgb_model import AssignmentModel from src.model.rgb_model import FailTransferResponseModel from src.model.rgb_model import ListTransferAssetWithBalanceResponseModel from src.model.rgb_model import SendAssetResponseModel @@ -25,7 +27,7 @@ from src.utils.info_message import INFO_FAIL_TRANSFER_SUCCESSFULLY from src.utils.info_message import INFO_REFRESH_SUCCESSFULLY from src.utils.page_navigation import PageNavigation -from src.viewmodels.rgb_25_view_model import RGB25ViewModel +from src.viewmodels.cfa_view_model import CFAViewModel from unit_tests.factories.transfer_assets import make_transfer_asset @@ -38,9 +40,9 @@ def setup_navigation(): @pytest.fixture -def rgb25_view_model(setup_navigation): - """Fixture to create an instance of the RGB25ViewModel class.""" - return RGB25ViewModel(setup_navigation) +def cfa_view_model(setup_navigation): + """Fixture to create an instance of the CFAViewModel class.""" + return CFAViewModel(setup_navigation) @pytest.fixture @@ -57,7 +59,12 @@ def mock_asset_details_response(): update_at_date='2024-06-13', updated_at_time='17:35:42', status='Settled', - amount=69, + assignment=AssignmentModel( + type=AssignmentEnumModel.FUNGIBLE, value=69, + ), + requested_assignment=AssignmentModel( + type=AssignmentEnumModel.FUNGIBLE, value=69, + ), amount_status='+69', kind='Issuance', transfer_Status=TransferStatusEnumModel.INTERNAL, @@ -74,12 +81,12 @@ def mock_asset_details_response(): @patch('src.data.service.asset_detail_page_services.AssetDetailPageService.get_asset_transactions') -def test_get_rgb25_asset_detail_success(mock_get_asset_transactions, rgb25_view_model, mock_asset_details_response): - """Test for successfully retrieving RGB25 asset detail.""" +def test_get_cfa_asset_detail_success(mock_get_asset_transactions, cfa_view_model, mock_asset_details_response): + """Test for successfully retrieving CFA asset detail.""" mock_txn_list_loaded_signal = Mock() mock_is_loading_signal = Mock() - rgb25_view_model.txn_list_loaded.connect(mock_txn_list_loaded_signal) - rgb25_view_model.is_loading.connect(mock_is_loading_signal) + cfa_view_model.txn_list_loaded.connect(mock_txn_list_loaded_signal) + cfa_view_model.is_loading.connect(mock_is_loading_signal) mock_get_asset_transactions.return_value = mock_asset_details_response @@ -88,24 +95,24 @@ def test_get_rgb25_asset_detail_success(mock_get_asset_transactions, rgb25_view_ image_path = 'test_image_path' asset_type = 'test_asset_type' - rgb25_view_model.get_rgb25_asset_detail( + cfa_view_model.get_cfa_asset_detail( asset_id, asset_name, image_path, asset_type, ) - rgb25_view_model.worker.result.emit(mock_asset_details_response) + cfa_view_model.worker.result.emit(mock_asset_details_response) mock_txn_list_loaded_signal.assert_called_once_with( asset_id, asset_name, image_path, asset_type, ) mock_is_loading_signal.assert_called_with(False) - assert rgb25_view_model.asset_id == asset_id - assert rgb25_view_model.asset_name == asset_name - assert rgb25_view_model.image_path == image_path - assert rgb25_view_model.asset_type == asset_type + assert cfa_view_model.asset_id == asset_id + assert cfa_view_model.asset_name == asset_name + assert cfa_view_model.image_path == image_path + assert cfa_view_model.asset_type == asset_type @patch('src.utils.worker.ThreadManager.run_in_thread', autospec=True) -def test_on_send_click(mock_run_in_thread, rgb25_view_model): - """Test the on_send_click method of RGB25ViewModel without executing the actual method.""" +def test_on_send_click(mock_run_in_thread, cfa_view_model): + """Test the on_send_click method of CFAViewModel without executing the actual method.""" # Setup test parameters amount = 100 blinded_utxo = 'test_blinded_utxo' @@ -114,33 +121,33 @@ def test_on_send_click(mock_run_in_thread, rgb25_view_model): min_confirmation = 1 # Ensure asset_id is set before calling the method - rgb25_view_model.asset_id = 'test_asset_id' + cfa_view_model.asset_id = 'test_asset_id' # Mock the worker object mock_worker = MagicMock() - rgb25_view_model.worker = mock_worker + cfa_view_model.worker = mock_worker # Call the method - rgb25_view_model.on_send_click( + cfa_view_model.on_send_click( amount, blinded_utxo, transport_endpoints, fee_rate, min_confirmation, ) # Verify the state changes - assert rgb25_view_model.amount == amount - assert rgb25_view_model.blinded_utxo == blinded_utxo - assert rgb25_view_model.transport_endpoints == transport_endpoints - assert rgb25_view_model.fee_rate == fee_rate - assert rgb25_view_model.min_confirmation == min_confirmation + assert cfa_view_model.amount == amount + assert cfa_view_model.blinded_utxo == blinded_utxo + assert cfa_view_model.transport_endpoints == transport_endpoints + assert cfa_view_model.fee_rate == fee_rate + assert cfa_view_model.min_confirmation == min_confirmation @patch('src.data.repository.rgb_repository.RgbRepository.fail_transfer') @patch('src.views.components.toast.ToastManager.success') @patch('src.views.components.toast.ToastManager.error') -def test_on_fail_transfer(mock_toast_error, mock_toast_success, mock_fail_transfer, rgb25_view_model): +def test_on_fail_transfer(mock_toast_error, mock_toast_success, mock_fail_transfer, cfa_view_model): """Test for handling fail transfer operation.""" # Mock necessary attributes and methods - rgb25_view_model.is_loading = MagicMock() - rgb25_view_model.get_rgb25_asset_detail = MagicMock() + cfa_view_model.is_loading = MagicMock() + cfa_view_model.get_cfa_asset_detail = MagicMock() # Mock the repository method return value mock_fail_transfer.return_value = FailTransferResponseModel( @@ -152,24 +159,24 @@ def mock_run_in_thread(func, args): # Simulate the success callback being called args['callback'](mock_fail_transfer.return_value) - rgb25_view_model.run_in_thread = MagicMock(side_effect=mock_run_in_thread) + cfa_view_model.run_in_thread = MagicMock(side_effect=mock_run_in_thread) # Set up the asset ID - rgb25_view_model.asset_id = 'test_asset_id' + cfa_view_model.asset_id = 'test_asset_id' # Call the method batch_transfer_idx = 1 - rgb25_view_model.on_fail_transfer(batch_transfer_idx) + cfa_view_model.on_fail_transfer(batch_transfer_idx) # Verify the behavior - rgb25_view_model.is_loading.emit.assert_called_with( + cfa_view_model.is_loading.emit.assert_called_with( True, ) # Loading state is set mock_toast_success.assert_called_once_with( description=INFO_FAIL_TRANSFER_SUCCESSFULLY, ) # Success toast is shown - rgb25_view_model.get_rgb25_asset_detail.assert_called_once_with( - rgb25_view_model.asset_id, rgb25_view_model.asset_name, None, rgb25_view_model.asset_type, + cfa_view_model.get_cfa_asset_detail.assert_called_once_with( + cfa_view_model.asset_id, cfa_view_model.asset_name, None, cfa_view_model.asset_type, ) # Test when transfers_changed=False @@ -179,13 +186,13 @@ def mock_run_in_thread_fail(func, args): # Simulate the error callback being called args['callback'](mock_fail_transfer.return_value) - rgb25_view_model.run_in_thread = MagicMock( + cfa_view_model.run_in_thread = MagicMock( side_effect=mock_run_in_thread_fail, ) - rgb25_view_model.on_fail_transfer(batch_transfer_idx) + cfa_view_model.on_fail_transfer(batch_transfer_idx) - rgb25_view_model.is_loading.emit.assert_called_with( + cfa_view_model.is_loading.emit.assert_called_with( False, ) # Loading state is unset mock_toast_error.assert_called_once_with( @@ -195,13 +202,13 @@ def mock_run_in_thread_fail(func, args): # Test for handling error while failing a transfer mock_exception = CommonException('Error sending asset') mock_fail_transfer.side_effect = mock_exception - rgb25_view_model.run_in_thread = MagicMock( + cfa_view_model.run_in_thread = MagicMock( side_effect=lambda func, args: args['error_callback'](mock_exception), ) - rgb25_view_model.on_fail_transfer(batch_transfer_idx) + cfa_view_model.on_fail_transfer(batch_transfer_idx) - rgb25_view_model.is_loading.emit.assert_called_with( + cfa_view_model.is_loading.emit.assert_called_with( False, ) # Loading state is unset mock_toast_error.assert_called_with( @@ -210,13 +217,13 @@ def mock_run_in_thread_fail(func, args): # Test for handling generic exception while failing a transfer mock_fail_transfer.side_effect = Exception('Generic error') - rgb25_view_model.run_in_thread = MagicMock( + cfa_view_model.run_in_thread = MagicMock( side_effect=lambda func, args: func(), ) - rgb25_view_model.on_fail_transfer(batch_transfer_idx) + cfa_view_model.on_fail_transfer(batch_transfer_idx) - rgb25_view_model.is_loading.emit.assert_called_with( + cfa_view_model.is_loading.emit.assert_called_with( False, ) # Loading state is unset mock_toast_error.assert_called_with( @@ -226,10 +233,10 @@ def mock_run_in_thread_fail(func, args): # Test on_refresh_click functionality -@patch('src.viewmodels.rgb_25_view_model.Cache') -@patch('src.viewmodels.rgb_25_view_model.ToastManager') -@patch('src.viewmodels.rgb_25_view_model.RgbRepository') -def test_on_refresh_click(mock_rgb_repository, mock_toast_manager, mock_cache, rgb25_view_model): +@patch('src.viewmodels.cfa_view_model.Cache') +@patch('src.viewmodels.cfa_view_model.ToastManager') +@patch('src.viewmodels.cfa_view_model.RgbRepository') +def test_on_refresh_click(mock_rgb_repository, mock_toast_manager, mock_cache, cfa_view_model): """Test the on_refresh_click method behavior.""" # Set up mocks mock_cache_session = MagicMock() @@ -238,60 +245,60 @@ def test_on_refresh_click(mock_rgb_repository, mock_toast_manager, mock_cache, r mock_toast_error = mock_toast_manager.error # Set up the view model - rgb25_view_model.refresh = MagicMock() - rgb25_view_model.is_loading = MagicMock() - rgb25_view_model.send_rgb25_button_clicked = MagicMock() - rgb25_view_model.get_rgb25_asset_detail = MagicMock() - rgb25_view_model.asset_id = 'test_asset_id' - rgb25_view_model.asset_name = 'test_asset' - rgb25_view_model.asset_type = 'RGB25' + cfa_view_model.refresh = MagicMock() + cfa_view_model.is_loading = MagicMock() + cfa_view_model.send_cfa_button_clicked = MagicMock() + cfa_view_model.get_cfa_asset_detail = MagicMock() + cfa_view_model.asset_id = 'test_asset_id' + cfa_view_model.asset_name = 'test_asset' + cfa_view_model.asset_type = 'CFA' # Test successful refresh def mock_run_in_thread(func, args): args['callback']() - rgb25_view_model.run_in_thread = MagicMock(side_effect=mock_run_in_thread) + cfa_view_model.run_in_thread = MagicMock(side_effect=mock_run_in_thread) # Call the method - rgb25_view_model.on_refresh_click() + cfa_view_model.on_refresh_click() # Verify behavior for successful case mock_cache_session.invalidate_cache.assert_called_once() - rgb25_view_model.send_rgb25_button_clicked.emit.assert_called_once_with( + cfa_view_model.send_cfa_button_clicked.emit.assert_called_once_with( True, ) - rgb25_view_model.is_loading.emit.assert_has_calls( + cfa_view_model.is_loading.emit.assert_has_calls( [call(True), call(False)], ) - rgb25_view_model.refresh.emit.assert_called_once_with(True) + cfa_view_model.refresh.emit.assert_called_once_with(True) mock_toast_success.assert_called_once_with( description=INFO_REFRESH_SUCCESSFULLY, ) - rgb25_view_model.get_rgb25_asset_detail.assert_called_once_with( - rgb25_view_model.asset_id, - rgb25_view_model.asset_name, + cfa_view_model.get_cfa_asset_detail.assert_called_once_with( + cfa_view_model.asset_id, + cfa_view_model.asset_name, None, - rgb25_view_model.asset_type, + cfa_view_model.asset_type, ) # Test error case with CommonException mock_exception = CommonException('Refresh error') - rgb25_view_model.run_in_thread = MagicMock( + cfa_view_model.run_in_thread = MagicMock( side_effect=lambda func, args: args['error_callback'](mock_exception), ) # Reset mocks - rgb25_view_model.refresh.reset_mock() - rgb25_view_model.is_loading.reset_mock() + cfa_view_model.refresh.reset_mock() + cfa_view_model.is_loading.reset_mock() mock_toast_error.reset_mock() mock_cache_session.invalidate_cache.reset_mock() # Call the method - rgb25_view_model.on_refresh_click() + cfa_view_model.on_refresh_click() # Verify behavior for error case - rgb25_view_model.refresh.emit.assert_called_once_with(False) - rgb25_view_model.is_loading.emit.assert_has_calls( + cfa_view_model.refresh.emit.assert_called_once_with(False) + cfa_view_model.is_loading.emit.assert_has_calls( [call(True), call(False)], ) mock_toast_error.assert_called_once_with( @@ -300,20 +307,20 @@ def mock_run_in_thread(func, args): # Test generic exception case generic_exception = Exception('Generic error') - rgb25_view_model.run_in_thread = MagicMock(side_effect=generic_exception) + cfa_view_model.run_in_thread = MagicMock(side_effect=generic_exception) # Reset mocks - rgb25_view_model.refresh.reset_mock() - rgb25_view_model.is_loading.reset_mock() + cfa_view_model.refresh.reset_mock() + cfa_view_model.is_loading.reset_mock() mock_toast_error.reset_mock() mock_cache_session.invalidate_cache.reset_mock() # Call the method - rgb25_view_model.on_refresh_click() + cfa_view_model.on_refresh_click() # Verify behavior for generic exception case - rgb25_view_model.refresh.emit.assert_called_once_with(False) - rgb25_view_model.is_loading.emit.assert_has_calls( + cfa_view_model.refresh.emit.assert_called_once_with(False) + cfa_view_model.is_loading.emit.assert_has_calls( [call(True), call(False)], ) mock_toast_error.assert_called_once_with( @@ -322,183 +329,183 @@ def mock_run_in_thread(func, args): # Test when cache is None mock_cache.get_cache_session.return_value = None - rgb25_view_model.run_in_thread = MagicMock(side_effect=mock_run_in_thread) + cfa_view_model.run_in_thread = MagicMock(side_effect=mock_run_in_thread) # Reset mocks - rgb25_view_model.refresh.reset_mock() - rgb25_view_model.is_loading.reset_mock() + cfa_view_model.refresh.reset_mock() + cfa_view_model.is_loading.reset_mock() mock_toast_success.reset_mock() mock_cache_session.invalidate_cache.reset_mock() # Call the method - rgb25_view_model.on_refresh_click() + cfa_view_model.on_refresh_click() # Verify behavior when cache is None # Should not be called when cache is None mock_cache_session.invalidate_cache.assert_not_called() - rgb25_view_model.send_rgb25_button_clicked.emit.assert_called_with(True) - rgb25_view_model.is_loading.emit.assert_has_calls( + cfa_view_model.send_cfa_button_clicked.emit.assert_called_with(True) + cfa_view_model.is_loading.emit.assert_has_calls( [call(True), call(False)], ) - rgb25_view_model.refresh.emit.assert_called_once_with(True) + cfa_view_model.refresh.emit.assert_called_once_with(True) mock_toast_success.assert_called_once_with( description=INFO_REFRESH_SUCCESSFULLY, ) -def test_on_success_send_rgb_asset(rgb25_view_model, mocker): +def test_on_success_send_rgb_asset(cfa_view_model, mocker): """Test on_success_send_rgb_asset behavior""" - rgb25_view_model.send_rgb25_button_clicked = MagicMock() - rgb25_view_model.is_loading = MagicMock() - rgb25_view_model.on_error = MagicMock() # Mock on_error as MagicMock + cfa_view_model.send_cfa_button_clicked = MagicMock() + cfa_view_model.is_loading = MagicMock() + cfa_view_model.on_error = MagicMock() # Mock on_error as MagicMock # Mock toast_error mock_toast_error = mocker.patch( - 'src.viewmodels.rgb_25_view_model.ToastManager.error', + 'src.viewmodels.cfa_view_model.ToastManager.error', ) # Test successful case - rgb25_view_model.asset_id = 'test_asset_id' - rgb25_view_model.amount = 100 - rgb25_view_model.blinded_utxo = 'test_blinded_utxo' - rgb25_view_model.transport_endpoints = ['endpoint1', 'endpoint2'] - rgb25_view_model.fee_rate = 1.0 - rgb25_view_model.min_confirmation = 1 - rgb25_view_model.run_in_thread = MagicMock() + cfa_view_model.asset_id = 'test_asset_id' + cfa_view_model.amount = 100 + cfa_view_model.blinded_utxo = 'test_blinded_utxo' + cfa_view_model.transport_endpoints = ['endpoint1', 'endpoint2'] + cfa_view_model.fee_rate = 1.0 + cfa_view_model.min_confirmation = 1 + cfa_view_model.run_in_thread = MagicMock() # Call method with success=True - rgb25_view_model.on_success_send_rgb_asset(True) + cfa_view_model.on_success_send_rgb_asset(True) # Verify behavior for success case - rgb25_view_model.send_rgb25_button_clicked.emit.assert_called_once_with( + cfa_view_model.send_cfa_button_clicked.emit.assert_called_once_with( True, ) - rgb25_view_model.is_loading.emit.assert_called_once_with(True) - rgb25_view_model.run_in_thread.assert_called_once() + cfa_view_model.is_loading.emit.assert_called_once_with(True) + cfa_view_model.run_in_thread.assert_called_once() # Verify run_in_thread arguments - call_args = rgb25_view_model.run_in_thread.call_args[0][1] - assert call_args['args'][0].asset_id == rgb25_view_model.asset_id - assert call_args['args'][0].amount == rgb25_view_model.amount - assert call_args['args'][0].recipient_id == rgb25_view_model.blinded_utxo - assert call_args['args'][0].transport_endpoints == rgb25_view_model.transport_endpoints - assert call_args['args'][0].fee_rate == rgb25_view_model.fee_rate - assert call_args['args'][0].min_confirmations == rgb25_view_model.min_confirmation - assert call_args['callback'] == rgb25_view_model.on_success_rgb25 - assert call_args['error_callback'] == rgb25_view_model.on_error + call_args = cfa_view_model.run_in_thread.call_args[0][1] + assert call_args['args'][0].asset_id == cfa_view_model.asset_id + assert call_args['args'][0].assignment.value == cfa_view_model.amount + assert call_args['args'][0].recipient_id == cfa_view_model.blinded_utxo + assert call_args['args'][0].transport_endpoints == cfa_view_model.transport_endpoints + assert call_args['args'][0].fee_rate == cfa_view_model.fee_rate + assert call_args['args'][0].min_confirmations == cfa_view_model.min_confirmation + assert call_args['callback'] == cfa_view_model.on_success_cfa + assert call_args['error_callback'] == cfa_view_model.on_error # Test exception case mock_exception = Exception('Test error') - rgb25_view_model.run_in_thread = MagicMock(side_effect=mock_exception) + cfa_view_model.run_in_thread = MagicMock(side_effect=mock_exception) # Reset mocks - rgb25_view_model.send_rgb25_button_clicked.reset_mock() - rgb25_view_model.is_loading.reset_mock() - rgb25_view_model.on_error.reset_mock() + cfa_view_model.send_cfa_button_clicked.reset_mock() + cfa_view_model.is_loading.reset_mock() + cfa_view_model.on_error.reset_mock() mock_toast_error.reset_mock() # Call method with success=True (will raise exception) - rgb25_view_model.on_success_send_rgb_asset(True) + cfa_view_model.on_success_send_rgb_asset(True) # Verify behavior for exception case - rgb25_view_model.send_rgb25_button_clicked.emit.assert_has_calls([ + cfa_view_model.send_cfa_button_clicked.emit.assert_has_calls([ call(True), ]) - rgb25_view_model.is_loading.emit.assert_has_calls([call(True)]) - rgb25_view_model.on_error.assert_called_once() + cfa_view_model.is_loading.emit.assert_has_calls([call(True)]) + cfa_view_model.on_error.assert_called_once() assert isinstance( - rgb25_view_model.on_error.call_args[0][0], CommonException, + cfa_view_model.on_error.call_args[0][0], CommonException, ) assert str(mock_exception) in str( - rgb25_view_model.on_error.call_args[0][0], + cfa_view_model.on_error.call_args[0][0], ) # Test authentication cancelled case # Reset mocks - rgb25_view_model.send_rgb25_button_clicked.reset_mock() - rgb25_view_model.is_loading.reset_mock() + cfa_view_model.send_cfa_button_clicked.reset_mock() + cfa_view_model.is_loading.reset_mock() mock_toast_error.reset_mock() # Call method with success=False - rgb25_view_model.on_success_send_rgb_asset(False) + cfa_view_model.on_success_send_rgb_asset(False) # Verify behavior for cancelled case - rgb25_view_model.send_rgb25_button_clicked.emit.assert_not_called() - rgb25_view_model.is_loading.emit.assert_not_called() + cfa_view_model.send_cfa_button_clicked.emit.assert_not_called() + cfa_view_model.is_loading.emit.assert_not_called() mock_toast_error.assert_called_once_with( description=ERROR_AUTHENTICATION_CANCELLED, ) -def test_on_error(rgb25_view_model, mocker): - """Test the on_error method of RGB25ViewModel.""" - rgb25_view_model.is_loading = MagicMock() - rgb25_view_model.send_rgb25_button_clicked = MagicMock() +def test_on_error(cfa_view_model, mocker): + """Test the on_error method of CFAViewModel.""" + cfa_view_model.is_loading = MagicMock() + cfa_view_model.send_cfa_button_clicked = MagicMock() # Create test error mock_error = CommonException('Test error message') mock_toast_error = mocker.patch( - 'src.viewmodels.rgb_25_view_model.ToastManager.error', + 'src.viewmodels.cfa_view_model.ToastManager.error', ) # Reset mocks - rgb25_view_model.is_loading.reset_mock() - rgb25_view_model.send_rgb25_button_clicked.reset_mock() + cfa_view_model.is_loading.reset_mock() + cfa_view_model.send_cfa_button_clicked.reset_mock() mock_toast_error.reset_mock() # Call on_error method - rgb25_view_model.on_error(mock_error) + cfa_view_model.on_error(mock_error) # Verify behavior - rgb25_view_model.is_loading.emit.assert_called_once_with(False) - rgb25_view_model.send_rgb25_button_clicked.emit.assert_called_once_with( + cfa_view_model.is_loading.emit.assert_called_once_with(False) + cfa_view_model.send_cfa_button_clicked.emit.assert_called_once_with( False, ) mock_toast_error.assert_called_once_with(description=mock_error.message) -def test_on_success_rgb25(rgb25_view_model, mocker): - """Test the on_success_rgb25 method of RGB25ViewModel.""" +def test_on_success_cfa(cfa_view_model, mocker): + """Test the on_success_cfa method of CFAViewModel.""" # Setup mocks - rgb25_view_model.is_loading = MagicMock() - rgb25_view_model.send_rgb25_button_clicked = MagicMock() - rgb25_view_model._page_navigation = MagicMock() + cfa_view_model.is_loading = MagicMock() + cfa_view_model.send_cfa_button_clicked = MagicMock() + cfa_view_model._page_navigation = MagicMock() mock_toast_success = mocker.patch( - 'src.viewmodels.rgb_25_view_model.ToastManager.success', + 'src.viewmodels.cfa_view_model.ToastManager.success', ) # Create test tx_id mock_tx_id = SendAssetResponseModel(txid='test_txid_123') - # Test RGB25 asset type - rgb25_view_model.asset_type = AssetType.RGB25.value - rgb25_view_model.on_success_rgb25(mock_tx_id) + # Test CFA asset type + cfa_view_model.asset_type = AssetType.CFA.value + cfa_view_model.on_success_cfa(mock_tx_id) - # Verify behavior for RGB25 - rgb25_view_model.is_loading.emit.assert_called_once_with(False) - rgb25_view_model.send_rgb25_button_clicked.emit.assert_called_once_with( + # Verify behavior for CFA + cfa_view_model.is_loading.emit.assert_called_once_with(False) + cfa_view_model.send_cfa_button_clicked.emit.assert_called_once_with( False, ) mock_toast_success.assert_called_once_with( description=INFO_ASSET_SENT.format(mock_tx_id.txid), ) - rgb25_view_model._page_navigation.collectibles_asset_page.assert_called_once() + cfa_view_model._page_navigation.collectibles_asset_page.assert_called_once() # Reset mocks - rgb25_view_model.is_loading.reset_mock() - rgb25_view_model.send_rgb25_button_clicked.reset_mock() - rgb25_view_model._page_navigation.reset_mock() + cfa_view_model.is_loading.reset_mock() + cfa_view_model.send_cfa_button_clicked.reset_mock() + cfa_view_model._page_navigation.reset_mock() mock_toast_success.reset_mock() - # Test RGB20 asset type - rgb25_view_model.asset_type = AssetType.RGB20.value - rgb25_view_model.on_success_rgb25(mock_tx_id) + # Test NIA asset type + cfa_view_model.asset_type = AssetType.NIA.value + cfa_view_model.on_success_cfa(mock_tx_id) - # Verify behavior for RGB20 - rgb25_view_model.is_loading.emit.assert_called_once_with(False) - rgb25_view_model.send_rgb25_button_clicked.emit.assert_called_once_with( + # Verify behavior for NIA + cfa_view_model.is_loading.emit.assert_called_once_with(False) + cfa_view_model.send_cfa_button_clicked.emit.assert_called_once_with( False, ) mock_toast_success.assert_called_once_with( description=INFO_ASSET_SENT.format(mock_tx_id.txid), ) - rgb25_view_model._page_navigation.fungibles_asset_page.assert_called_once() + cfa_view_model._page_navigation.fungibles_asset_page.assert_called_once() diff --git a/unit_tests/tests/viewmodel_tests/channel_management_viewmodel_test.py b/unit_tests/tests/viewmodel_tests/channel_management_viewmodel_test.py index 0ffa6997..74a7e6c4 100644 --- a/unit_tests/tests/viewmodel_tests/channel_management_viewmodel_test.py +++ b/unit_tests/tests/viewmodel_tests/channel_management_viewmodel_test.py @@ -18,7 +18,9 @@ from src.model.rgb_model import GetAssetResponseModel from src.utils.custom_exception import CommonException from src.utils.error_message import ERROR_CREATE_UTXO -from src.utils.error_message import ERROR_INSUFFICIENT_ALLOCATION_SLOT +from src.utils.error_message import ERROR_INSUFFICIENT_ASSET +from src.utils.error_message import ERROR_INVALID_PUBKEY_TYPE +from src.utils.error_message import ERROR_NO_UNCOLORED_UTXOS_AVAILABLE from src.utils.error_message import ERROR_SOMETHING_WENT_WRONG from src.utils.info_message import INFO_CHANNEL_DELETED from src.viewmodels.channel_management_viewmodel import ChannelManagementViewModel @@ -256,7 +258,6 @@ def test_get_asset_name(channel_view_model): mock_nia_asset = AssetModel( asset_id='1', name='NIA Asset', - asset_iface='interface1', details=None, precision=2, issued_supply=4000, @@ -269,7 +270,6 @@ def test_get_asset_name(channel_view_model): mock_cfa_asset = AssetModel( asset_id='2', name='CFA Asset', - asset_iface='interface2', details=None, precision=2, issued_supply=1000, @@ -308,7 +308,6 @@ def test_get_asset_list_success_with_both_assets(channel_view_model, mocker): mock_nia_asset = AssetModel( asset_id='1', name='Asset1', - asset_iface='interface1', details=None, precision=2, issued_supply=1000, @@ -322,7 +321,6 @@ def test_get_asset_list_success_with_both_assets(channel_view_model, mocker): mock_cfa_asset = AssetModel( asset_id='2', name='Asset2', - asset_iface='interface2', details=None, precision=2, issued_supply=2000, @@ -453,7 +451,7 @@ def test_create_rgb_channel_insufficient_allocation_error(channel_view_model, mo channel_view_model.handle_insufficient_allocation = MagicMock() channel_view_model.run_in_thread = MagicMock() - error_message = ERROR_INSUFFICIENT_ALLOCATION_SLOT + error_message = ERROR_NO_UNCOLORED_UTXOS_AVAILABLE mock_error = CommonException(error_message) # Act @@ -491,6 +489,64 @@ def test_navigate_to_create_channel_page(channel_view_model, mocker): mock_page_navigation.create_channel_page.assert_called_once() +def test_create_rgb_channel_hard_stop_invalid_pubkey(channel_view_model, mocker): + """Ensure invalid pubkey error shows toast and does not retry creating UTXOs.""" + # Arrange + channel_view_model.is_loading = MagicMock() + channel_view_model.handle_insufficient_allocation = MagicMock() + channel_view_model.run_in_thread = MagicMock() + mock_toast_error = mocker.patch( + 'src.views.components.toast.ToastManager.error', + ) + + mock_error = CommonException(ERROR_INVALID_PUBKEY_TYPE) + + # Act + channel_view_model.create_rgb_channel( + pub_key='pub_key', asset_id='asset_id', amount=1000, capacity_sat='30000', push_msat='0', + ) + + run_thread_kwargs = channel_view_model.run_in_thread.call_args[0][1] + on_error = run_thread_kwargs['error_callback'] + on_error(mock_error) + + # Assert + channel_view_model.is_loading.emit.assert_called_with(False) + mock_toast_error.assert_called_once_with( + description=ERROR_INVALID_PUBKEY_TYPE, + ) + channel_view_model.handle_insufficient_allocation.assert_not_called() + + +def test_create_rgb_channel_hard_stop_insufficient_asset(channel_view_model, mocker): + """Ensure not enough assets error shows toast and does not retry creating UTXOs.""" + # Arrange + channel_view_model.is_loading = MagicMock() + channel_view_model.handle_insufficient_allocation = MagicMock() + channel_view_model.run_in_thread = MagicMock() + mock_toast_error = mocker.patch( + 'src.views.components.toast.ToastManager.error', + ) + + mock_error = CommonException(ERROR_INSUFFICIENT_ASSET) + + # Act + channel_view_model.create_rgb_channel( + pub_key='pub_key', asset_id='asset_id', amount=1000, capacity_sat='30000', push_msat='0', + ) + + run_thread_kwargs = channel_view_model.run_in_thread.call_args[0][1] + on_error = run_thread_kwargs['error_callback'] + on_error(mock_error) + + # Assert + channel_view_model.is_loading.emit.assert_called_with(False) + mock_toast_error.assert_called_once_with( + description=ERROR_INSUFFICIENT_ASSET, + ) + channel_view_model.handle_insufficient_allocation.assert_not_called() + + def test_handle_insufficient_allocation(channel_view_model, mocker): """Test handle_insufficient_allocation method.""" # Arrange diff --git a/unit_tests/tests/viewmodel_tests/header_frame_view_model_test.py b/unit_tests/tests/viewmodel_tests/header_frame_view_model_test.py index 18f0f61a..4486d150 100644 --- a/unit_tests/tests/viewmodel_tests/header_frame_view_model_test.py +++ b/unit_tests/tests/viewmodel_tests/header_frame_view_model_test.py @@ -85,6 +85,9 @@ def test_header_frame_view_model_init(mocker): def test_header_frame_view_model_network_check(mocker): """Test that start_network_check creates a NetworkCheckerThread and starts it.""" + # Mock QTimer to prevent side effects during init + mocker.patch('src.viewmodels.header_frame_view_model.QTimer') + view_model = HeaderFrameViewModel() mock_thread = mocker.patch( @@ -101,8 +104,11 @@ def test_header_frame_view_model_network_check(mocker): mock_instance.start.assert_called_once() -def test_header_frame_view_model_handle_network_status(): +def test_header_frame_view_model_handle_network_status(mocker): """Test that handle_network_status emits the correct signal.""" + # Mock QTimer to prevent it from starting and creating circular references + mocker.patch('src.viewmodels.header_frame_view_model.QTimer') + view_model = HeaderFrameViewModel() received_signals = [] @@ -119,10 +125,17 @@ def signal_received(value): def test_header_frame_view_model_stop_network_checker(mocker): """Test that stop_network_checker stops the timer.""" + # Mock QTimer so we can verify stop() is called without starting real timer + mock_timer_class = mocker.patch( + 'src.viewmodels.header_frame_view_model.QTimer', + ) + mock_timer_instance = mock_timer_class.return_value + view_model = HeaderFrameViewModel() - mock_timer = mocker.patch.object(view_model.timer, 'stop') + # Reset mock to clear init calls + mock_timer_instance.stop.reset_mock() view_model.stop_network_checker() - mock_timer.assert_called_once() + mock_timer_instance.stop.assert_called_once() diff --git a/unit_tests/tests/viewmodel_tests/issue_cfa_view_model_test.py b/unit_tests/tests/viewmodel_tests/issue_cfa_view_model_test.py new file mode 100644 index 00000000..a9e7c5c5 --- /dev/null +++ b/unit_tests/tests/viewmodel_tests/issue_cfa_view_model_test.py @@ -0,0 +1,289 @@ +"""Unit test for Issue CFA view model. + +This module contains tests for the IssueCFAViewModel class, which represents the view model +for the Issue CFA Asset page activities. +""" +# Disable the redefined-outer-name warning as +# it's normal to pass mocked object in tests function +# pylint: disable=redefined-outer-name,unused-argument +from __future__ import annotations + +from unittest.mock import MagicMock +from unittest.mock import Mock +from unittest.mock import patch + +import pytest +from PySide6.QtWidgets import QFileDialog + +from src.model.rgb_model import AssetBalanceResponseModel +from src.model.rgb_model import IssueAssetResponseModel +from src.utils.custom_exception import CommonException +from src.utils.error_message import ERROR_AUTHENTICATION +from src.utils.error_message import ERROR_FIELD_MISSING +from src.utils.error_message import ERROR_SOMETHING_WENT_WRONG +from src.utils.info_message import INFO_ASSET_ISSUED +from src.utils.info_message import INFO_NO_FILE +from src.viewmodels.issue_cfa_view_model import IssueCFAViewModel + + +@pytest.fixture +def mock_page_navigation(mocker): + """Fixture to create a mock page navigation object.""" + return mocker.MagicMock() + + +@pytest.fixture +def issue_cfa_view_model(mock_page_navigation): + """Fixture to create an instance of the IssueCFAViewModel class.""" + return IssueCFAViewModel(mock_page_navigation) + + +@patch('src.views.components.toast.ToastManager') +def test_open_file_dialog_normal_execution(mock_toast_manager, issue_cfa_view_model): + """Test open_file_dialog method when a file is selected""" + with patch.object(QFileDialog, 'exec_', return_value=True), \ + patch.object(QFileDialog, 'selectedFiles', return_value=['/path/to/file.png']): + + # Create a mock signal to check if the file_upload_message signal is emitted + mock_signal = Mock() + issue_cfa_view_model.file_upload_message.connect(mock_signal) + + issue_cfa_view_model.open_file_dialog() + + # Verify that the signal is emitted with the correct file path + mock_signal.assert_called_once_with('/path/to/file.png') + + +@patch('src.views.components.toast.ToastManager.error') +@patch('src.data.repository.rgb_repository.RgbRepository.issue_asset_cfa') +@patch('src.utils.worker.ThreadManager.run_in_thread') +def test_issue_cfa_asset_failure( + mock_run_in_thread, mock_issue_asset_cfa, mock_toast_error, + issue_cfa_view_model, mock_page_navigation, +): + """Test for failure to issue CFA asset.""" + # Simulate failure in issuing the asset + mock_issue_asset_cfa.side_effect = CommonException('Failed to issue asset') + + # Mock the worker + mock_worker = MagicMock() + issue_cfa_view_model.worker = mock_worker + + # Provide required input data + issue_cfa_view_model.uploaded_file_path = 'path/to/file.png' + + # Perform the action + issue_cfa_view_model.issue_cfa_asset('ticker', 'asset_name', '100') + + # Simulate the error callback + mock_worker.error.emit(mock_issue_asset_cfa.side_effect) + + mock_page_navigation.collectibles_asset_page.assert_not_called() + + +@patch('src.views.components.toast.ToastManager') +def test_open_file_dialog_success(mock_toast_manager, issue_cfa_view_model, mocker): + """Test for open_file_dialog method when file is successfully selected""" + with patch('PySide6.QtWidgets.QFileDialog.exec_', return_value=True), \ + patch('PySide6.QtWidgets.QFileDialog.selectedFiles', return_value=['/path/to/selected/image.png']): + issue_cfa_view_model.open_file_dialog() + + assert issue_cfa_view_model.uploaded_file_path == '/path/to/selected/image.png' + + +@patch('src.views.components.toast.ToastManager') +def test_open_file_dialog_no_selection(mock_toast_manager, issue_cfa_view_model, mocker): + """Test for open_file_dialog method when no file is selected""" + with patch('PySide6.QtWidgets.QFileDialog.exec_', return_value=False): + issue_cfa_view_model.open_file_dialog() + + assert issue_cfa_view_model.uploaded_file_path is None + + +@patch('src.views.components.toast.ToastManager.error') +def test_open_file_dialog_exception(mock_toast_manager, issue_cfa_view_model, mocker): + """Test for open_file_dialog method when an exception is raised""" + with patch('PySide6.QtWidgets.QFileDialog.exec_', side_effect=CommonException('Test error')): + issue_cfa_view_model.open_file_dialog() + + mock_toast_manager.assert_called_once_with( + description='An unexpected error occurred: Test error', + ) + + +@patch('src.views.components.toast.ToastManager.error') +def test_on_success_native_auth_cfa_missing_fields(mock_toast_manager, issue_cfa_view_model): + """Test on_success_native_auth_cfa with missing required fields""" + issue_cfa_view_model.is_loading = MagicMock() + + issue_cfa_view_model.amount = None + issue_cfa_view_model.asset_name = None + issue_cfa_view_model.asset_ticker = None + + issue_cfa_view_model.on_success_native_auth_cfa(True) + + mock_toast_manager.assert_called_once_with(description=ERROR_FIELD_MISSING) + issue_cfa_view_model.is_loading.emit.assert_called_with(False) + + +@patch('src.views.components.toast.ToastManager.error') +def test_on_error_native_auth_cfa(mock_toast_manager, issue_cfa_view_model): + """Test on_error_native_auth_cfa with different error types""" + # Test with CommonException + common_error = CommonException('Test error') + issue_cfa_view_model.on_error_native_auth_cfa(common_error) + mock_toast_manager.assert_called_with(description='Test error') + + # Test with generic Exception + generic_error = Exception('Generic error') + issue_cfa_view_model.on_error_native_auth_cfa(generic_error) + mock_toast_manager.assert_called_with( + description=ERROR_SOMETHING_WENT_WRONG, + ) + + +@patch('src.views.components.toast.ToastManager.success') +def test_on_success(mock_toast_manager, issue_cfa_view_model): + """Test on_success callback""" + issue_cfa_view_model.success_page_message = MagicMock() + issue_cfa_view_model.is_loading = MagicMock() + + response = IssueAssetResponseModel( + asset_id='test_id', + name='Test Asset', + ticker='TEST', + details='details', + precision=2, + issued_supply=1000, + timestamp=123456789, + added_at=123456789, + balance=AssetBalanceResponseModel( + spendable=10, future=10, settled=12, offchain_outbound=0, offchain_inbound=0, + ), + ) + + issue_cfa_view_model.on_success(response) + + mock_toast_manager.assert_called_once_with( + description=INFO_ASSET_ISSUED.format('test_id'), + ) + issue_cfa_view_model.success_page_message.emit.assert_called_once_with( + 'Test Asset', + ) + issue_cfa_view_model.is_loading.emit.assert_called_once_with(False) + + +@patch('src.views.components.toast.ToastManager.error') +def test_on_error(mock_toast_manager, issue_cfa_view_model): + """Test on_error callback""" + issue_cfa_view_model.is_loading = MagicMock() + error = CommonException('Test error') + + issue_cfa_view_model.on_error(error) + + mock_toast_manager.assert_called_once_with(description='Test error') + issue_cfa_view_model.is_loading.emit.assert_called_once_with(False) + + @patch('src.views.components.toast.ToastManager.error') + def test_on_success_native_auth_cfa_with_generic_exception(mock_toast_manager, issue_cfa_view_model): + """Test on_success_native_auth_cfa with generic exception""" + issue_cfa_view_model.is_loading = MagicMock() + issue_cfa_view_model.run_in_thread = MagicMock( + side_effect=Exception('Unexpected error'), + ) + + # Set required attributes + issue_cfa_view_model.amount = '100' + issue_cfa_view_model.asset_name = 'Test Asset' + issue_cfa_view_model.asset_ticker = 'TEST' + issue_cfa_view_model.uploaded_file_path = '/path/to/file.png' + + issue_cfa_view_model.on_success_native_auth_cfa(True) + + mock_toast_manager.assert_called_once_with( + description=ERROR_SOMETHING_WENT_WRONG, + ) + issue_cfa_view_model.is_loading.emit.assert_called_once_with(False) + + +@patch('src.views.components.toast.ToastManager.error') +def test_on_success_native_auth_cfa_auth_failed(mock_toast_manager, issue_cfa_view_model): + """Test on_success_native_auth_cfa when authentication fails""" + issue_cfa_view_model.is_loading = MagicMock() + + # Set required attributes + issue_cfa_view_model.amount = '100' + issue_cfa_view_model.asset_name = 'Test Asset' + issue_cfa_view_model.asset_ticker = 'TEST' + + issue_cfa_view_model.on_success_native_auth_cfa(False) + + mock_toast_manager.assert_called_once_with( + description=ERROR_AUTHENTICATION, + ) + issue_cfa_view_model.is_loading.emit.assert_called_once_with(False) + + +@patch('src.views.components.toast.ToastManager.error') +def test_on_success_native_auth_cfa_no_file(mock_toast_manager, issue_cfa_view_model): + """Test on_success_native_auth_cfa when no file is uploaded""" + issue_cfa_view_model.is_loading = MagicMock() + + # Set required attributes except file path + issue_cfa_view_model.amount = '100' + issue_cfa_view_model.asset_name = 'Test Asset' + issue_cfa_view_model.asset_ticker = 'TEST' + issue_cfa_view_model.uploaded_file_path = None + + issue_cfa_view_model.on_success_native_auth_cfa(True) + + mock_toast_manager.assert_called_once_with(description=INFO_NO_FILE) + issue_cfa_view_model.is_loading.emit.assert_called_once_with(False) + + +@patch('src.viewmodels.issue_cfa_view_model.IssueAssetService') +def test_on_success_native_auth_cfa_success(mock_issue_asset_service, issue_cfa_view_model): + """Test on_success_native_auth_cfa successful execution""" + issue_cfa_view_model.run_in_thread = MagicMock() + + # Set all required attributes + issue_cfa_view_model.amount = '100' + issue_cfa_view_model.asset_name = 'Test Asset' + issue_cfa_view_model.asset_ticker = 'TEST' + issue_cfa_view_model.uploaded_file_path = '/path/to/file.png' + + issue_cfa_view_model.on_success_native_auth_cfa(True) + + # Verify run_in_thread was called with correct arguments + issue_cfa_view_model.run_in_thread.assert_called_once() + call_args = issue_cfa_view_model.run_in_thread.call_args[0][1] + + assert call_args['callback'] == issue_cfa_view_model.on_success + assert call_args['error_callback'] == issue_cfa_view_model.on_error + assert len(call_args['args']) == 1 + + request_model = call_args['args'][0] + assert request_model.amounts == [100] + assert request_model.ticker == 'TEST' + assert request_model.name == 'Test Asset' + assert request_model.file_path == '/path/to/file.png' + + +@patch('src.views.components.toast.ToastManager.error') +def test_on_success_native_auth_cfa_exception(mock_toast_manager, issue_cfa_view_model): + """Test on_success_native_auth_cfa when an unexpected exception occurs""" + issue_cfa_view_model.is_loading = MagicMock() + + # Set all required attributes + # This will cause an exception when converting to int + issue_cfa_view_model.amount = 'invalid_amount' + issue_cfa_view_model.asset_name = 'Test Asset' + issue_cfa_view_model.asset_ticker = 'TEST' + issue_cfa_view_model.uploaded_file_path = '/path/to/file.png' + + issue_cfa_view_model.on_success_native_auth_cfa(True) + + mock_toast_manager.assert_called_once_with( + description=ERROR_SOMETHING_WENT_WRONG, + ) + issue_cfa_view_model.is_loading.emit.assert_called_once_with(False) diff --git a/unit_tests/tests/viewmodel_tests/issue_rgb20_view_model_test.py b/unit_tests/tests/viewmodel_tests/issue_nia_view_model_test.py similarity index 61% rename from unit_tests/tests/viewmodel_tests/issue_rgb20_view_model_test.py rename to unit_tests/tests/viewmodel_tests/issue_nia_view_model_test.py index 9cb98c5f..27e75d22 100644 --- a/unit_tests/tests/viewmodel_tests/issue_rgb20_view_model_test.py +++ b/unit_tests/tests/viewmodel_tests/issue_nia_view_model_test.py @@ -1,4 +1,4 @@ -"""Unit test for issue RGB20 view model""" +"""Unit test for issue NIA view model""" # Disable the redefined-outer-name warning as # it's normal to pass mocked object in tests function # pylint: disable=redefined-outer-name,unused-argument @@ -14,7 +14,7 @@ from src.model.rgb_model import IssueAssetResponseModel from src.utils.custom_exception import CommonException from src.utils.error_message import ERROR_SOMETHING_WENT_WRONG -from src.viewmodels.issue_rgb20_view_model import IssueRGB20ViewModel +from src.viewmodels.issue_nia_view_model import IssueNIAViewModel @pytest.fixture @@ -24,9 +24,9 @@ def mock_page_navigation(mocker): @pytest.fixture -def issue_rgb20_view_model(mock_page_navigation): - """Fixture to create an instance of the IssueRGB20ViewModel class.""" - return IssueRGB20ViewModel(mock_page_navigation) +def issue_nia_view_model(mock_page_navigation): + """Fixture to create an instance of the IssueNIAViewModel class.""" + return IssueNIAViewModel(mock_page_navigation) @patch('src.views.components.toast.ToastManager.error') @@ -34,13 +34,12 @@ def issue_rgb20_view_model(mock_page_navigation): @patch('src.utils.worker.ThreadManager.run_in_thread') def test_on_issue_click_success( mock_run_in_thread, mock_issue_asset_nia, mock_toast_error, - issue_rgb20_view_model, mock_page_navigation, + issue_nia_view_model, mock_page_navigation, ): - """Test for successful issuing of RGB20 asset.""" + """Test for successful issuing of NIA asset.""" # Mock the asset issuance response mock_issue_asset_nia.return_value = IssueAssetResponseModel( asset_id='asset_id', - asset_iface='interface', ticker='ticker', name='name', details='details', @@ -55,17 +54,17 @@ def test_on_issue_click_success( # Mock signals mock_issue_button_clicked = Mock() - issue_rgb20_view_model.issue_button_clicked.connect( + issue_nia_view_model.issue_button_clicked.connect( mock_issue_button_clicked, ) # Mock worker mock_worker = MagicMock() - issue_rgb20_view_model.worker = mock_worker + issue_nia_view_model.worker = mock_worker mock_worker.result.emit = Mock() # Perform the action - issue_rgb20_view_model.on_issue_click( + issue_nia_view_model.on_issue_click( 'short_identifier', 'asset_name', '100', ) @@ -83,14 +82,13 @@ def test_on_issue_click_success( @patch('src.utils.worker.ThreadManager.run_in_thread') def test_on_success_native_auth( mock_run_in_thread, mock_native_authentication, mock_issue_asset_nia, - mock_toast_error, issue_rgb20_view_model, mock_page_navigation, + mock_toast_error, issue_nia_view_model, mock_page_navigation, ): """Test for successful native authentication and asset issuance.""" # Mock native authentication and asset issuance mock_native_authentication.return_value = True mock_issue_asset_nia.return_value = IssueAssetResponseModel( asset_id='asset_id', - asset_iface='interface', ticker='ticker', name='name', details='details', @@ -105,37 +103,37 @@ def test_on_success_native_auth( # Connect signals to mocks mock_issue_button_clicked = MagicMock() - issue_rgb20_view_model.issue_button_clicked.connect( + issue_nia_view_model.issue_button_clicked.connect( mock_issue_button_clicked, ) mock_is_issued = MagicMock() - issue_rgb20_view_model.is_issued.connect(mock_is_issued) + issue_nia_view_model.is_issued.connect(mock_is_issued) # Set test data - issue_rgb20_view_model.token_amount = '100' - issue_rgb20_view_model.asset_name = 'asset_name' - issue_rgb20_view_model.short_identifier = 'short_identifier' + issue_nia_view_model.token_amount = '100' + issue_nia_view_model.asset_name = 'asset_name' + issue_nia_view_model.short_identifier = 'short_identifier' # Simulate success callback for native authentication - issue_rgb20_view_model.on_success_native_auth_rgb20(success=True) + issue_nia_view_model.on_success_native_auth_nia(success=True) # Simulate worker behavior mock_worker = MagicMock() - issue_rgb20_view_model.worker = mock_worker + issue_nia_view_model.worker = mock_worker mock_worker.result.emit(mock_issue_asset_nia.return_value) mock_toast_error.assert_not_called() def test_on_success_native_auth_generic_exception( - issue_rgb20_view_model, + issue_nia_view_model, ): """Test for handling generic Exception in on_success_native_auth.""" # Setup with patch('src.views.components.toast.ToastManager.error') as mock_show_toast: # Trigger the exception - issue_rgb20_view_model.on_success_native_auth_rgb20(success=False) + issue_nia_view_model.on_success_native_auth_nia(success=False) # Verify the call to show_toast mock_show_toast.assert_called_once_with( @@ -144,16 +142,16 @@ def test_on_success_native_auth_generic_exception( @patch('src.views.components.toast.ToastManager.error') -def test_on_success_native_auth_rgb20_missing_value(mock_toast_manager, issue_rgb20_view_model): - """Test on_success_native_auth_rgb25 when an unexpected exception occurs""" +def test_on_success_native_auth_nia_missing_value(mock_toast_manager, issue_nia_view_model): + """Test on_success_native_auth_cfa when an unexpected exception occurs""" # Set all required attributes # This will cause an exception when converting to int - issue_rgb20_view_model.amount = '' - issue_rgb20_view_model.asset_name = 'Test Asset' - issue_rgb20_view_model.asset_ticker = 'TEST' + issue_nia_view_model.amount = '' + issue_nia_view_model.asset_name = 'Test Asset' + issue_nia_view_model.asset_ticker = 'TEST' - issue_rgb20_view_model.on_success_native_auth_rgb20(True) + issue_nia_view_model.on_success_native_auth_nia(True) mock_toast_manager.assert_called_once_with( description='Few fields missing', @@ -161,76 +159,76 @@ def test_on_success_native_auth_rgb20_missing_value(mock_toast_manager, issue_rg @patch('src.views.components.toast.ToastManager.error') -def test_on_success_native_auth_rgb20_exception(mock_toast_manager, issue_rgb20_view_model): - """Test on_success_native_auth_rgb25 when an unexpected exception occurs""" - issue_rgb20_view_model.issue_button_clicked = MagicMock() +def test_on_success_native_auth_nia_exception(mock_toast_manager, issue_nia_view_model): + """Test on_success_native_auth_cfa when an unexpected exception occurs""" + issue_nia_view_model.issue_button_clicked = MagicMock() # Set required attributes - issue_rgb20_view_model.token_amount = '100' - issue_rgb20_view_model.asset_name = 'Test Asset' - issue_rgb20_view_model.short_identifier = 'TEST' + issue_nia_view_model.token_amount = '100' + issue_nia_view_model.asset_name = 'Test Asset' + issue_nia_view_model.short_identifier = 'TEST' # Mock run_in_thread to raise an exception def mock_run_in_thread(*args, **kwargs): raise RuntimeError('Test exception') # Patch run_in_thread method - with patch.object(issue_rgb20_view_model, 'run_in_thread', side_effect=mock_run_in_thread): - issue_rgb20_view_model.on_success_native_auth_rgb20(True) + with patch.object(issue_nia_view_model, 'run_in_thread', side_effect=mock_run_in_thread): + issue_nia_view_model.on_success_native_auth_nia(True) mock_toast_manager.assert_called_once_with( description=ERROR_SOMETHING_WENT_WRONG, ) - issue_rgb20_view_model.issue_button_clicked.emit.assert_called_once_with( + issue_nia_view_model.issue_button_clicked.emit.assert_called_once_with( False, ) @patch('src.views.components.toast.ToastManager.error') -def test_on_error_native_auth_rgb20_common_exception(mock_toast_manager, issue_rgb20_view_model): - """Test on_error_native_auth_rgb20 with CommonException""" - issue_rgb20_view_model.issue_button_clicked = MagicMock() +def test_on_error_native_auth_nia_common_exception(mock_toast_manager, issue_nia_view_model): + """Test on_error_native_auth_nia with CommonException""" + issue_nia_view_model.issue_button_clicked = MagicMock() test_message = 'Test error message' test_error = CommonException(message=test_message) - issue_rgb20_view_model.on_error_native_auth_rgb20(test_error) + issue_nia_view_model.on_error_native_auth_nia(test_error) mock_toast_manager.assert_called_once_with(description=test_message) - issue_rgb20_view_model.issue_button_clicked.emit.assert_called_once_with( + issue_nia_view_model.issue_button_clicked.emit.assert_called_once_with( False, ) @patch('src.views.components.toast.ToastManager.error') -def test_on_error_native_auth_rgb20_generic_exception(mock_toast_manager, issue_rgb20_view_model): - """Test on_error_native_auth_rgb20 with generic Exception""" - issue_rgb20_view_model.issue_button_clicked = MagicMock() +def test_on_error_native_auth_nia_generic_exception(mock_toast_manager, issue_nia_view_model): + """Test on_error_native_auth_nia with generic Exception""" + issue_nia_view_model.issue_button_clicked = MagicMock() test_error = Exception('Test error') - issue_rgb20_view_model.on_error_native_auth_rgb20(test_error) + issue_nia_view_model.on_error_native_auth_nia(test_error) mock_toast_manager.assert_called_once_with( description=ERROR_SOMETHING_WENT_WRONG, ) - issue_rgb20_view_model.issue_button_clicked.emit.assert_called_once_with( + issue_nia_view_model.issue_button_clicked.emit.assert_called_once_with( False, ) @patch('src.views.components.toast.ToastManager.error') -def test_on_error(mock_toast_manager, issue_rgb20_view_model): - """Test on_error method for RGB20 issue page""" +def test_on_error(mock_toast_manager, issue_nia_view_model): + """Test on_error method for NIA issue page""" # Setup - issue_rgb20_view_model.issue_button_clicked = MagicMock() + issue_nia_view_model.issue_button_clicked = MagicMock() test_message = 'Test error message' test_error = MagicMock() test_error.message = test_message # Execute - issue_rgb20_view_model.on_error(test_error) + issue_nia_view_model.on_error(test_error) # Assert mock_toast_manager.assert_called_once_with(description=test_message) - issue_rgb20_view_model.issue_button_clicked.emit.assert_called_once_with( + issue_nia_view_model.issue_button_clicked.emit.assert_called_once_with( False, ) diff --git a/unit_tests/tests/viewmodel_tests/issue_rgb25_view_model_test.py b/unit_tests/tests/viewmodel_tests/issue_rgb25_view_model_test.py deleted file mode 100644 index 3ce20707..00000000 --- a/unit_tests/tests/viewmodel_tests/issue_rgb25_view_model_test.py +++ /dev/null @@ -1,290 +0,0 @@ -"""Unit test for Issue RGB25 view model. - -This module contains tests for the IssueRGB25ViewModel class, which represents the view model -for the Issue RGB25 Asset page activities. -""" -# Disable the redefined-outer-name warning as -# it's normal to pass mocked object in tests function -# pylint: disable=redefined-outer-name,unused-argument -from __future__ import annotations - -from unittest.mock import MagicMock -from unittest.mock import Mock -from unittest.mock import patch - -import pytest -from PySide6.QtWidgets import QFileDialog - -from src.model.rgb_model import AssetBalanceResponseModel -from src.model.rgb_model import IssueAssetResponseModel -from src.utils.custom_exception import CommonException -from src.utils.error_message import ERROR_AUTHENTICATION -from src.utils.error_message import ERROR_FIELD_MISSING -from src.utils.error_message import ERROR_SOMETHING_WENT_WRONG -from src.utils.info_message import INFO_ASSET_ISSUED -from src.utils.info_message import INFO_NO_FILE -from src.viewmodels.issue_rgb25_view_model import IssueRGB25ViewModel - - -@pytest.fixture -def mock_page_navigation(mocker): - """Fixture to create a mock page navigation object.""" - return mocker.MagicMock() - - -@pytest.fixture -def issue_rgb25_view_model(mock_page_navigation): - """Fixture to create an instance of the IssueRGB25ViewModel class.""" - return IssueRGB25ViewModel(mock_page_navigation) - - -@patch('src.views.components.toast.ToastManager') -def test_open_file_dialog_normal_execution(mock_toast_manager, issue_rgb25_view_model): - """Test open_file_dialog method when a file is selected""" - with patch.object(QFileDialog, 'exec_', return_value=True), \ - patch.object(QFileDialog, 'selectedFiles', return_value=['/path/to/file.png']): - - # Create a mock signal to check if the file_upload_message signal is emitted - mock_signal = Mock() - issue_rgb25_view_model.file_upload_message.connect(mock_signal) - - issue_rgb25_view_model.open_file_dialog() - - # Verify that the signal is emitted with the correct file path - mock_signal.assert_called_once_with('/path/to/file.png') - - -@patch('src.views.components.toast.ToastManager.error') -@patch('src.data.repository.rgb_repository.RgbRepository.issue_asset_cfa') -@patch('src.utils.worker.ThreadManager.run_in_thread') -def test_issue_rgb25_asset_failure( - mock_run_in_thread, mock_issue_asset_cfa, mock_toast_error, - issue_rgb25_view_model, mock_page_navigation, -): - """Test for failure to issue RGB25 asset.""" - # Simulate failure in issuing the asset - mock_issue_asset_cfa.side_effect = CommonException('Failed to issue asset') - - # Mock the worker - mock_worker = MagicMock() - issue_rgb25_view_model.worker = mock_worker - - # Provide required input data - issue_rgb25_view_model.uploaded_file_path = 'path/to/file.png' - - # Perform the action - issue_rgb25_view_model.issue_rgb25_asset('ticker', 'asset_name', '100') - - # Simulate the error callback - mock_worker.error.emit(mock_issue_asset_cfa.side_effect) - - mock_page_navigation.collectibles_asset_page.assert_not_called() - - -@patch('src.views.components.toast.ToastManager') -def test_open_file_dialog_success(mock_toast_manager, issue_rgb25_view_model, mocker): - """Test for open_file_dialog method when file is successfully selected""" - with patch('PySide6.QtWidgets.QFileDialog.exec_', return_value=True), \ - patch('PySide6.QtWidgets.QFileDialog.selectedFiles', return_value=['/path/to/selected/image.png']): - issue_rgb25_view_model.open_file_dialog() - - assert issue_rgb25_view_model.uploaded_file_path == '/path/to/selected/image.png' - - -@patch('src.views.components.toast.ToastManager') -def test_open_file_dialog_no_selection(mock_toast_manager, issue_rgb25_view_model, mocker): - """Test for open_file_dialog method when no file is selected""" - with patch('PySide6.QtWidgets.QFileDialog.exec_', return_value=False): - issue_rgb25_view_model.open_file_dialog() - - assert issue_rgb25_view_model.uploaded_file_path is None - - -@patch('src.views.components.toast.ToastManager.error') -def test_open_file_dialog_exception(mock_toast_manager, issue_rgb25_view_model, mocker): - """Test for open_file_dialog method when an exception is raised""" - with patch('PySide6.QtWidgets.QFileDialog.exec_', side_effect=CommonException('Test error')): - issue_rgb25_view_model.open_file_dialog() - - mock_toast_manager.assert_called_once_with( - description='An unexpected error occurred: Test error', - ) - - -@patch('src.views.components.toast.ToastManager.error') -def test_on_success_native_auth_rgb25_missing_fields(mock_toast_manager, issue_rgb25_view_model): - """Test on_success_native_auth_rgb25 with missing required fields""" - issue_rgb25_view_model.is_loading = MagicMock() - - issue_rgb25_view_model.amount = None - issue_rgb25_view_model.asset_name = None - issue_rgb25_view_model.asset_ticker = None - - issue_rgb25_view_model.on_success_native_auth_rgb25(True) - - mock_toast_manager.assert_called_once_with(description=ERROR_FIELD_MISSING) - issue_rgb25_view_model.is_loading.emit.assert_called_with(False) - - -@patch('src.views.components.toast.ToastManager.error') -def test_on_error_native_auth_rgb25(mock_toast_manager, issue_rgb25_view_model): - """Test on_error_native_auth_rgb25 with different error types""" - # Test with CommonException - common_error = CommonException('Test error') - issue_rgb25_view_model.on_error_native_auth_rgb25(common_error) - mock_toast_manager.assert_called_with(description='Test error') - - # Test with generic Exception - generic_error = Exception('Generic error') - issue_rgb25_view_model.on_error_native_auth_rgb25(generic_error) - mock_toast_manager.assert_called_with( - description=ERROR_SOMETHING_WENT_WRONG, - ) - - -@patch('src.views.components.toast.ToastManager.success') -def test_on_success(mock_toast_manager, issue_rgb25_view_model): - """Test on_success callback""" - issue_rgb25_view_model.success_page_message = MagicMock() - issue_rgb25_view_model.is_loading = MagicMock() - - response = IssueAssetResponseModel( - asset_id='test_id', - name='Test Asset', - asset_iface='interface', - ticker='TEST', - details='details', - precision=2, - issued_supply=1000, - timestamp=123456789, - added_at=123456789, - balance=AssetBalanceResponseModel( - spendable=10, future=10, settled=12, offchain_outbound=0, offchain_inbound=0, - ), - ) - - issue_rgb25_view_model.on_success(response) - - mock_toast_manager.assert_called_once_with( - description=INFO_ASSET_ISSUED.format('test_id'), - ) - issue_rgb25_view_model.success_page_message.emit.assert_called_once_with( - 'Test Asset', - ) - issue_rgb25_view_model.is_loading.emit.assert_called_once_with(False) - - -@patch('src.views.components.toast.ToastManager.error') -def test_on_error(mock_toast_manager, issue_rgb25_view_model): - """Test on_error callback""" - issue_rgb25_view_model.is_loading = MagicMock() - error = CommonException('Test error') - - issue_rgb25_view_model.on_error(error) - - mock_toast_manager.assert_called_once_with(description='Test error') - issue_rgb25_view_model.is_loading.emit.assert_called_once_with(False) - - @patch('src.views.components.toast.ToastManager.error') - def test_on_success_native_auth_rgb25_with_generic_exception(mock_toast_manager, issue_rgb25_view_model): - """Test on_success_native_auth_rgb25 with generic exception""" - issue_rgb25_view_model.is_loading = MagicMock() - issue_rgb25_view_model.run_in_thread = MagicMock( - side_effect=Exception('Unexpected error'), - ) - - # Set required attributes - issue_rgb25_view_model.amount = '100' - issue_rgb25_view_model.asset_name = 'Test Asset' - issue_rgb25_view_model.asset_ticker = 'TEST' - issue_rgb25_view_model.uploaded_file_path = '/path/to/file.png' - - issue_rgb25_view_model.on_success_native_auth_rgb25(True) - - mock_toast_manager.assert_called_once_with( - description=ERROR_SOMETHING_WENT_WRONG, - ) - issue_rgb25_view_model.is_loading.emit.assert_called_once_with(False) - - -@patch('src.views.components.toast.ToastManager.error') -def test_on_success_native_auth_rgb25_auth_failed(mock_toast_manager, issue_rgb25_view_model): - """Test on_success_native_auth_rgb25 when authentication fails""" - issue_rgb25_view_model.is_loading = MagicMock() - - # Set required attributes - issue_rgb25_view_model.amount = '100' - issue_rgb25_view_model.asset_name = 'Test Asset' - issue_rgb25_view_model.asset_ticker = 'TEST' - - issue_rgb25_view_model.on_success_native_auth_rgb25(False) - - mock_toast_manager.assert_called_once_with( - description=ERROR_AUTHENTICATION, - ) - issue_rgb25_view_model.is_loading.emit.assert_called_once_with(False) - - -@patch('src.views.components.toast.ToastManager.error') -def test_on_success_native_auth_rgb25_no_file(mock_toast_manager, issue_rgb25_view_model): - """Test on_success_native_auth_rgb25 when no file is uploaded""" - issue_rgb25_view_model.is_loading = MagicMock() - - # Set required attributes except file path - issue_rgb25_view_model.amount = '100' - issue_rgb25_view_model.asset_name = 'Test Asset' - issue_rgb25_view_model.asset_ticker = 'TEST' - issue_rgb25_view_model.uploaded_file_path = None - - issue_rgb25_view_model.on_success_native_auth_rgb25(True) - - mock_toast_manager.assert_called_once_with(description=INFO_NO_FILE) - issue_rgb25_view_model.is_loading.emit.assert_called_once_with(False) - - -@patch('src.viewmodels.issue_rgb25_view_model.IssueAssetService') -def test_on_success_native_auth_rgb25_success(mock_issue_asset_service, issue_rgb25_view_model): - """Test on_success_native_auth_rgb25 successful execution""" - issue_rgb25_view_model.run_in_thread = MagicMock() - - # Set all required attributes - issue_rgb25_view_model.amount = '100' - issue_rgb25_view_model.asset_name = 'Test Asset' - issue_rgb25_view_model.asset_ticker = 'TEST' - issue_rgb25_view_model.uploaded_file_path = '/path/to/file.png' - - issue_rgb25_view_model.on_success_native_auth_rgb25(True) - - # Verify run_in_thread was called with correct arguments - issue_rgb25_view_model.run_in_thread.assert_called_once() - call_args = issue_rgb25_view_model.run_in_thread.call_args[0][1] - - assert call_args['callback'] == issue_rgb25_view_model.on_success - assert call_args['error_callback'] == issue_rgb25_view_model.on_error - assert len(call_args['args']) == 1 - - request_model = call_args['args'][0] - assert request_model.amounts == [100] - assert request_model.ticker == 'TEST' - assert request_model.name == 'Test Asset' - assert request_model.file_path == '/path/to/file.png' - - -@patch('src.views.components.toast.ToastManager.error') -def test_on_success_native_auth_rgb25_exception(mock_toast_manager, issue_rgb25_view_model): - """Test on_success_native_auth_rgb25 when an unexpected exception occurs""" - issue_rgb25_view_model.is_loading = MagicMock() - - # Set all required attributes - # This will cause an exception when converting to int - issue_rgb25_view_model.amount = 'invalid_amount' - issue_rgb25_view_model.asset_name = 'Test Asset' - issue_rgb25_view_model.asset_ticker = 'TEST' - issue_rgb25_view_model.uploaded_file_path = '/path/to/file.png' - - issue_rgb25_view_model.on_success_native_auth_rgb25(True) - - mock_toast_manager.assert_called_once_with( - description=ERROR_SOMETHING_WENT_WRONG, - ) - issue_rgb25_view_model.is_loading.emit.assert_called_once_with(False) diff --git a/unit_tests/tests/viewmodel_tests/main_asset_view_model_test.py b/unit_tests/tests/viewmodel_tests/main_asset_view_model_test.py index a0c3b8ed..324f591f 100644 --- a/unit_tests/tests/viewmodel_tests/main_asset_view_model_test.py +++ b/unit_tests/tests/viewmodel_tests/main_asset_view_model_test.py @@ -45,11 +45,10 @@ def mock_main_page_data_response(): nia=[ AssetModel( asset_id='1', - asset_iface='interface1', - name='Asset1', details=None, - precision=2, + name='Asset1', issued_supply=1000, + precision=2, timestamp=1620000000, added_at=1620001000, balance=AssetBalanceResponseModel( @@ -58,11 +57,10 @@ def mock_main_page_data_response(): ), AssetModel( asset_id='2', - asset_iface='interface2', - name='Asset2', details=None, - precision=2, + name='Asset2', issued_supply=2000, + precision=2, timestamp=1620002000, added_at=1620003000, balance=AssetBalanceResponseModel( @@ -73,7 +71,6 @@ def mock_main_page_data_response(): uda=[ AssetModel( asset_id='3', - asset_iface='interface3', name='Asset3', details=None, precision=2, @@ -86,7 +83,6 @@ def mock_main_page_data_response(): ), AssetModel( asset_id='4', - asset_iface='interface4', name='Asset4', details=None, precision=2, @@ -101,7 +97,6 @@ def mock_main_page_data_response(): cfa=[ AssetModel( asset_id='5', - asset_iface='interface5', name='Asset5', details=None, precision=2, @@ -114,7 +109,6 @@ def mock_main_page_data_response(): ), AssetModel( asset_id='6', - asset_iface='interface6', name='Asset6', details=None, precision=2, @@ -215,13 +209,13 @@ def mock_run_in_thread(func, kwargs): list_loaded_mock.assert_called_once_with(False) -def test_navigate_issue_rgb20_with_enough_balance( +def test_navigate_issue_nia_with_enough_balance( main_asset_view_model, mock_btc_repository, mock_btc_balance_response_with_positive, mock_page_navigation, ): - """Test navigation to issue rgb20 page with enough balance.""" + """Test navigation to issue nia page with enough balance.""" # Mock signals message_mock = Mock() main_asset_view_model.message.connect(message_mock) diff --git a/unit_tests/tests/viewmodel_tests/main_view_model_test.py b/unit_tests/tests/viewmodel_tests/main_view_model_test.py index c1eaf87d..7b459eb4 100644 --- a/unit_tests/tests/viewmodel_tests/main_view_model_test.py +++ b/unit_tests/tests/viewmodel_tests/main_view_model_test.py @@ -22,18 +22,18 @@ def test_main_view_model_initialization(): assert view_model.welcome_view_model is not None assert view_model.terms_view_model is not None assert view_model.main_asset_view_model is not None - assert view_model.issue_rgb20_asset_view_model is not None + assert view_model.issue_nia_asset_view_model is not None assert view_model.set_wallet_password_view_model is not None assert view_model.bitcoin_view_model is not None assert view_model.receive_bitcoin_view_model is not None assert view_model.send_bitcoin_view_model is not None assert view_model.channel_view_model is not None assert view_model.unspent_view_model is not None - assert view_model.issue_rgb25_asset_view_model is not None + assert view_model.issue_cfa_asset_view_model is not None assert view_model.ln_endpoint_view_model is not None - assert view_model.rgb25_view_model is not None + assert view_model.cfa_view_model is not None assert view_model.enter_wallet_password_view_model is not None - assert view_model.receive_rgb25_view_model is not None + assert view_model.receive_cfa_view_model is not None assert view_model.backup_view_model is not None assert view_model.setting_view_model is not None assert view_model.ln_offchain_view_model is not None @@ -53,18 +53,18 @@ def test_main_view_model_initialization(): assert view_model.welcome_view_model._page_navigation == mock_page_navigation assert view_model.terms_view_model._page_navigation == mock_page_navigation assert view_model.main_asset_view_model._page_navigation == mock_page_navigation - assert view_model.issue_rgb20_asset_view_model._page_navigation == mock_page_navigation + assert view_model.issue_nia_asset_view_model._page_navigation == mock_page_navigation assert view_model.set_wallet_password_view_model._page_navigation == mock_page_navigation assert view_model.bitcoin_view_model._page_navigation == mock_page_navigation assert view_model.receive_bitcoin_view_model._page_navigation == mock_page_navigation assert view_model.send_bitcoin_view_model._page_navigation == mock_page_navigation assert view_model.channel_view_model._page_navigation == mock_page_navigation assert view_model.unspent_view_model._page_navigation == mock_page_navigation - assert view_model.issue_rgb25_asset_view_model._page_navigation == mock_page_navigation + assert view_model.issue_cfa_asset_view_model._page_navigation == mock_page_navigation assert view_model.ln_endpoint_view_model._page_navigation == mock_page_navigation - assert view_model.rgb25_view_model._page_navigation == mock_page_navigation + assert view_model.cfa_view_model._page_navigation == mock_page_navigation assert view_model.enter_wallet_password_view_model._page_navigation == mock_page_navigation - assert view_model.receive_rgb25_view_model._page_navigation == mock_page_navigation + assert view_model.receive_cfa_view_model._page_navigation == mock_page_navigation assert view_model.backup_view_model._page_navigation == mock_page_navigation assert view_model.setting_view_model._page_navigation == mock_page_navigation assert view_model.ln_offchain_view_model._page_navigation == mock_page_navigation diff --git a/unit_tests/tests/viewmodel_tests/receive_rgb25_view_model_test.py b/unit_tests/tests/viewmodel_tests/receive_cfa_view_model_test.py similarity index 66% rename from unit_tests/tests/viewmodel_tests/receive_rgb25_view_model_test.py rename to unit_tests/tests/viewmodel_tests/receive_cfa_view_model_test.py index 322297af..3821df79 100644 --- a/unit_tests/tests/viewmodel_tests/receive_rgb25_view_model_test.py +++ b/unit_tests/tests/viewmodel_tests/receive_cfa_view_model_test.py @@ -1,4 +1,4 @@ -"""Unit test for ReceiveRGB25ViewModel""" +"""Unit test for ReceiveCFAViewModel""" # Disable the redefined-outer-name warning as # it's normal to pass mocked object in tests function # pylint: disable=redefined-outer-name,unused-argument,protected-access @@ -11,7 +11,7 @@ from src.model.rgb_model import RgbInvoiceDataResponseModel from src.utils.custom_exception import CommonException -from src.viewmodels.receive_rgb25_view_model import ReceiveRGB25ViewModel +from src.viewmodels.receive_cfa_view_model import ReceiveCFAViewModel @pytest.fixture @@ -21,18 +21,18 @@ def mock_page_navigation(mocker): @pytest.fixture -def receive_rgb25_view_model(mock_page_navigation): - """Fixture to create an instance of the ReceiveRGB25ViewModel class.""" - return ReceiveRGB25ViewModel(mock_page_navigation) +def receive_cfa_view_model(mock_page_navigation): + """Fixture to create an instance of the ReceiveCFAViewModel class.""" + return ReceiveCFAViewModel(mock_page_navigation) @patch('src.data.repository.rgb_repository.RgbRepository.rgb_invoice') -def test_get_rgb_invoice_success(mock_rgb_invoice, receive_rgb25_view_model): +def test_get_rgb_invoice_success(mock_rgb_invoice, receive_cfa_view_model): """Test for successfully retrieving an RGB invoice.""" mock_address_signal = Mock() - receive_rgb25_view_model.address.connect(mock_address_signal) + receive_cfa_view_model.address.connect(mock_address_signal) mock_loading_signal = Mock() - receive_rgb25_view_model.hide_loading.connect(mock_loading_signal) + receive_cfa_view_model.hide_loading.connect(mock_loading_signal) mock_invoice_response = RgbInvoiceDataResponseModel( recipient_id='recipient_id', @@ -44,24 +44,24 @@ def test_get_rgb_invoice_success(mock_rgb_invoice, receive_rgb25_view_model): mock_rgb_invoice.return_value = mock_invoice_response # Call get_rgb_invoice with a minimum confirmation argument - receive_rgb25_view_model.get_rgb_invoice(minimum_confirmations=1) - receive_rgb25_view_model.worker.result.emit(mock_invoice_response) + receive_cfa_view_model.get_rgb_invoice(minimum_confirmations=1) + receive_cfa_view_model.worker.result.emit(mock_invoice_response) mock_address_signal.assert_called_once_with(mock_invoice_response.invoice) mock_loading_signal.assert_called_once_with(False) @patch('src.views.components.toast.ToastManager.error') -def test_on_error_shows_error_and_navigates(mock_toast, receive_rgb25_view_model): +def test_on_error_shows_error_and_navigates(mock_toast, receive_cfa_view_model): """Test for handling errors in on_error method.""" mock_loading_signal = Mock() - receive_rgb25_view_model.hide_loading.connect(mock_loading_signal) + receive_cfa_view_model.hide_loading.connect(mock_loading_signal) # Create a mock error mock_error = CommonException('Error occurred') # Call the on_error method with the mock error - receive_rgb25_view_model.on_error(mock_error) + receive_cfa_view_model.on_error(mock_error) # Assert that the error toast was shown with the correct message mock_toast.assert_called_once_with( @@ -72,10 +72,10 @@ def test_on_error_shows_error_and_navigates(mock_toast, receive_rgb25_view_model mock_loading_signal.assert_called_once_with(False) # Assert that the navigation to fungibles asset page occurred - receive_rgb25_view_model._page_navigation.fungibles_asset_page.assert_called_once() + receive_cfa_view_model._page_navigation.fungibles_asset_page.assert_called_once() # Assert that the sidebar is checked sidebar_mock = Mock() - receive_rgb25_view_model._page_navigation.sidebar.return_value = sidebar_mock - receive_rgb25_view_model.on_error(mock_error) + receive_cfa_view_model._page_navigation.sidebar.return_value = sidebar_mock + receive_cfa_view_model.on_error(mock_error) sidebar_mock.my_fungibles.setChecked.assert_called_once_with(True) diff --git a/unit_tests/tests/viewmodel_tests/view_unspent_view_model_test.py b/unit_tests/tests/viewmodel_tests/view_unspent_view_model_test.py index fecd4196..58f4bf77 100644 --- a/unit_tests/tests/viewmodel_tests/view_unspent_view_model_test.py +++ b/unit_tests/tests/viewmodel_tests/view_unspent_view_model_test.py @@ -40,7 +40,10 @@ def mock_list_unspents_response(): 'rgb_allocations': [ { 'asset_id': 'rgb:2dkSTbr-jFhznbPmo-TQafzswCN-av4gTsJjX-ttx6CNou5-M98k8Zd', - 'amount': 42, + 'assignment': { + 'type': 'Fungible', + 'value': 42, + }, 'settled': False, }, ],