diff --git a/.github/workflows/update_compatible_version.yml b/.github/workflows/update_compatible_version.yml index 2f85d6e0..49b341ee 100644 --- a/.github/workflows/update_compatible_version.yml +++ b/.github/workflows/update_compatible_version.yml @@ -39,33 +39,38 @@ jobs: - name: Extract Versions from PR if: ${{ steps.check_version.outputs.current != steps.check_version.outputs.last }} id: extract_versions + shell: bash run: | - - PR_BODY="${{ github.event.pull_request.body }}" + # Dump PR body to a file WITHOUT bash interpreting backticks / $(...) + cat > pr_body.md <<'__PR_BODY__' + ${{ github.event.pull_request.body }} + __PR_BODY__ # Extract the "## Versions" section - echo "$PR_BODY" | awk '/## Versions/{flag=1; next} /^## /{flag=0} flag' > PR_VERSIONS.md + awk '/## Versions/{flag=1; next} /^## /{flag=0} flag' pr_body.md > PR_VERSIONS.md + + CLI_VERSION="$(grep -oP '(?<=- Pilot Release Version: ).*' PR_VERSIONS.md 2>/dev/null || true)" + CLI_VERSION="${CLI_VERSION//$'\r'/}" - # Read extracted content - CLI_VERSION=$(grep -oP '(?<=- Pilot Release Version: ).*' PR_VERSIONS.md | tr -d '\r') - COMPATIBLE_VERSION=$(grep -oP '(?<=- Compatible Version: ).*' PR_VERSIONS.md | tr -d '\r') + COMPATIBLE_VERSION="$(grep -oP '(?<=- Compatible Version: ).*' PR_VERSIONS.md 2>/dev/null || true)" + COMPATIBLE_VERSION="${COMPATIBLE_VERSION//$'\r'/}" - # Handle missing values CLI_VERSION=${CLI_VERSION:-"Unknown"} COMPATIBLE_VERSION=${COMPATIBLE_VERSION:-"Unknown"} - # Get the latest Git tag (assuming it's the CLI version) - LATEST_CLI_VERSION=$(grep '^version =' pyproject.toml | sed -E 's/version = "(.*)"/\1/') + LAST_VERSION="$( + tail -n 1 docs/compatible_version.ndjson 2>/dev/null \ + | jq -r '."Cli Version"' 2>/dev/null \ + || true + )" + LAST_VERSION=${LAST_VERSION:-""} - # Convert to NDJSON format - NEW_ENTRY="{\"Cli Version\": \"$LATEST_CLI_VERSION\", \"Pilot Release Version\": \"$CLI_VERSION\", \"Compatible Version\": \"$COMPATIBLE_VERSION\"}" + NEW_ENTRY="{\"Cli Version\": \"$LAST_VERSION\", \"Pilot Release Version\": \"$CLI_VERSION\", \"Compatible Version\": \"$COMPATIBLE_VERSION\"}" - # Ensure file exists before appending touch docs/compatible_version.ndjson - - # Append new entry to NDJSON file echo "$NEW_ENTRY" >> docs/compatible_version.ndjson + - name: Embed Compatible Versions into README if: ${{ steps.check_version.outputs.current != steps.check_version.outputs.last }} run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 16a4348e..8d76a910 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,19 +1,16 @@ repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v6.0.0 hooks: - id: check-added-large-files - - id: check-docstring-first - id: check-merge-conflict - id: check-toml - id: check-yaml - - id: double-quote-string-fixer - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 22.8.0 + rev: 26.1.0 hooks: - id: black args: [ @@ -22,51 +19,35 @@ repos: ] - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 7.0.0 hooks: - id: isort args: [ '--line-length=120', '--profile=black', '--filter-files', - '--force-single-line-imports', - '--reverse-relative', ] - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 7.3.0 hooks: - id: flake8 additional_dependencies: [ - 'pycodestyle==2.9.1', # E,W - 'pyflakes==2.5.0', # F - 'mccabe==0.7.0', # C - 'flake8-bugbear==22.9.11', # B - 'flake8-builtins==1.5.3', # A - 'flake8-comprehensions==3.10.0', # C4 - 'flake8-debugger==4.1.2', # T1 - 'flake8-logging-format==0.7.5', # G - 'flake8-print==5.0.0', # T2 + 'flake8-bugbear', # B + 'flake8-builtins', # A + 'flake8-comprehensions', # C4 + 'flake8-debugger', # T1 + 'flake8-print', # T2 ] args: [ - '--select=E,W,F,C,B,A,C4,T1,G,T2', - '--ignore=E203,W503,B008,B305,A003,G004', + '--select=E,W,F,C,B,A,C4,T1,T2', + '--ignore=E203,W503,B008,B305,A003', '--max-complexity=10', '--max-line-length=120', ] - # - repo: https://github.com/myint/docformatter - # rev: v1.5.0 - # hooks: - # - id: docformatter - # args: [ - # '--wrap-summaries=120', - # '--wrap-descriptions=120', - # '--in-place', - # ] - - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.4.2 + rev: v1.5.6 hooks: - id: insert-license files: \.py$ diff --git a/README.md b/README.md index 8544334b..9b3491f3 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,8 @@ Command line tool that allows the user to execute data operations on the platfor | 3.19.6 | 2.15.2 | 2.15.2 | | 3.19.7 | 2.15.2 | 2.15.2 | | 3.20.0 | 2.16.0 | 2.15.2 | -| 3.20.1 | 2.16 | 2.16 | +| 3.20.1 | 2.16.0 | 2.16.0 | +| 3.20.1 | Unknown | Unknown | ## Build Instructions diff --git a/app/__init__.py b/app/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/commands/__init__.py b/app/commands/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/commands/__init__.py +++ b/app/commands/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/commands/container_registry.py b/app/commands/container_registry.py index f554b695..20600bd7 100644 --- a/app/commands/container_registry.py +++ b/app/commands/container_registry.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/commands/dataset.py b/app/commands/dataset.py index 1ad5f53d..29f314af 100644 --- a/app/commands/dataset.py +++ b/app/commands/dataset.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/commands/entry_point.py b/app/commands/entry_point.py index 289016ef..587ea31c 100644 --- a/app/commands/entry_point.py +++ b/app/commands/entry_point.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -8,29 +8,24 @@ from app.services.user_authentication.decorator import require_login_session -from .container_registry import create_project -from .container_registry import get_secret -from .container_registry import invite_member -from .container_registry import list_projects -from .container_registry import list_repositories -from .dataset import dataset_download -from .dataset import dataset_list -from .dataset import dataset_show_detail -from .file import file_check_manifest -from .file import file_download -from .file import file_export_manifest -from .file import file_list -from .file import file_metadata_download -from .file import file_move -from .file import file_put -from .file import file_resume -from .file import file_trash +from .container_registry import create_project, get_secret, invite_member, list_projects, list_repositories +from .dataset import dataset_download, dataset_list, dataset_show_detail +from .file import ( + file_check_manifest, + file_download, + file_export_manifest, + file_list, + file_metadata_download, + file_move, + file_put, + file_resume, + file_trash, +) from .folder import folder_create # Import custom commands from .project import project_list_all -from .user import login -from .user import logout +from .user import login, logout container_registry_enabled = os.environ.get('PILOT_CLI_CONTAINER_REGISTRY_ENABLED', 'false') == 'true' diff --git a/app/commands/file.py b/app/commands/file.py index 7ebc1f1c..611d8716 100644 --- a/app/commands/file.py +++ b/app/commands/file.py @@ -1,11 +1,11 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. import ast import json import os -from sys import exit +import sys import click from click.exceptions import Abort @@ -13,8 +13,7 @@ import app.services.output_manager.help_page as file_help import app.services.output_manager.message_handler as message_handler from app.configs.app_config import AppConfig -from app.models.item import ItemStatus -from app.models.item import ItemType +from app.models.item import ItemStatus, ItemType from app.services.file_manager.file_download.download_client import SrvFileDownload from app.services.file_manager.file_list import SrvFileList from app.services.file_manager.file_manifests import SrvFileManifests @@ -22,24 +21,22 @@ from app.services.file_manager.file_move.file_move_client import FileMoveClient from app.services.file_manager.file_trash.file_trash_client import FileTrashClient from app.services.file_manager.file_trash.utils import parse_trash_paths -from app.services.file_manager.file_upload.file_upload import assemble_path -from app.services.file_manager.file_upload.file_upload import resume_upload -from app.services.file_manager.file_upload.file_upload import simple_upload +from app.services.file_manager.file_upload.file_upload import assemble_path, resume_upload, simple_upload from app.services.file_manager.file_upload.upload_validator import UploadEventValidator from app.services.logger_services.debugging_log import debug_logger -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler -from app.services.output_manager.error_handler import customized_error_msg +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler, customized_error_msg from app.services.user_authentication.decorator import require_valid_token -from app.utils.aggregated import doc -from app.utils.aggregated import fit_terminal_width -from app.utils.aggregated import get_file_info_by_geid -from app.utils.aggregated import get_zone -from app.utils.aggregated import identify_target_folder -from app.utils.aggregated import normalize_input_paths -from app.utils.aggregated import normalize_join -from app.utils.aggregated import remove_the_output_file -from app.utils.aggregated import search_item +from app.utils.aggregated import ( + doc, + fit_terminal_width, + get_file_info_by_geid, + get_zone, + identify_target_folder, + normalize_input_paths, + normalize_join, + remove_the_output_file, + search_item, +) @click.command() @@ -159,7 +156,7 @@ def file_put(**kwargs): # noqa: C901 pass except Abort: message_handler.SrvOutPutHandler.cancel_upload() - exit(1) + sys.exit(1) # check if user input at least one file/folder if len(files) == 0: @@ -173,7 +170,7 @@ def file_put(**kwargs): # noqa: C901 ) except Abort: message_handler.SrvOutPutHandler.cancel_upload() - exit(1) + sys.exit(1) project_code, folder_type, target_folder = identify_target_folder(project_path) upload_val_event = { @@ -533,12 +530,12 @@ def file_move(**kwargs): message_handler.SrvOutPutHandler.move_action_failed( src_item_path, dest_item_path, 'Cannot move files between different projects' ) - exit(1) + sys.exit(1) elif len(src_item.split('/')) <= 2 or len(dest_item.split('/')) <= 1: message_handler.SrvOutPutHandler.move_action_failed( src_item_path, dest_item_path, 'Cannot move root/name/shared folders' ) - exit(1) + sys.exit(1) # tranlate keyword to correct object path src_keyword, src_path = src_item.split('/', 1) diff --git a/app/commands/folder.py b/app/commands/folder.py index f5e3b5bd..4eba6087 100644 --- a/app/commands/folder.py +++ b/app/commands/folder.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -7,14 +7,9 @@ import app.services.output_manager.message_handler as message_handler from app.configs.app_config import AppConfig from app.services.file_manager.file_metadata.folder_client import FolderClient -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler -from app.services.output_manager.help_page import FileHELP -from app.services.output_manager.help_page import FolderHelp -from app.services.output_manager.help_page import file_help_page -from app.services.output_manager.help_page import folder_help_page -from app.utils.aggregated import doc -from app.utils.aggregated import validate_folder_name +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler +from app.services.output_manager.help_page import FileHELP, FolderHelp, file_help_page, folder_help_page +from app.utils.aggregated import doc, validate_folder_name @click.command() diff --git a/app/commands/project.py b/app/commands/project.py index 496aa8c9..58aeb149 100644 --- a/app/commands/project.py +++ b/app/commands/project.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/commands/user.py b/app/commands/user.py index ec55ebee..8d145596 100644 --- a/app/commands/user.py +++ b/app/commands/user.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -13,13 +13,13 @@ import app.services.output_manager.message_handler as mhandler from app.models.enums import LoginMethod from app.services.user_authentication.decorator import require_login_session -from app.services.user_authentication.user_login_logout import login_using_api_key -from app.services.user_authentication.user_login_logout import user_device_id_login -from app.services.user_authentication.user_login_logout import user_logout -from app.services.user_authentication.user_login_logout import validate_user_device_login -from app.utils.aggregated import doc -from app.utils.aggregated import get_latest_cli_version -from app.utils.aggregated import get_version_compatibility +from app.services.user_authentication.user_login_logout import ( + login_using_api_key, + user_device_id_login, + user_logout, + validate_user_device_login, +) +from app.utils.aggregated import doc, get_latest_cli_version, get_version_compatibility @click.command() diff --git a/app/configs/__init__.py b/app/configs/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/configs/__init__.py +++ b/app/configs/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/configs/app_config.py b/app/configs/app_config.py index b9f19999..adeec17d 100644 --- a/app/configs/app_config.py +++ b/app/configs/app_config.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/configs/config.py b/app/configs/config.py index d0bef0bc..4ec2f8b2 100644 --- a/app/configs/config.py +++ b/app/configs/config.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -8,8 +8,7 @@ from typing import Set from pydantic import computed_field -from pydantic_settings import BaseSettings -from pydantic_settings import SettingsConfigDict +from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): diff --git a/app/configs/user_config.py b/app/configs/user_config.py index ecf10dd7..05dbe2e8 100644 --- a/app/configs/user_config.py +++ b/app/configs/user_config.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -8,21 +8,19 @@ import time from enum import IntEnum from pathlib import Path -from typing import Iterable -from typing import Union +from typing import Iterable, Union from app.configs.config import ConfigClass -from app.configs.utils import check_owner_linux -from app.configs.utils import check_owner_windows -from app.configs.utils import check_user_permission_linux -from app.configs.utils import check_user_permission_windows -from app.configs.utils import create_directory_with_permissions_windows +from app.configs.utils import ( + check_owner_linux, + check_owner_windows, + check_user_permission_linux, + check_user_permission_windows, + create_directory_with_permissions_windows, +) from app.models.singleton import Singleton -from app.services.crypto.crypto import decryption -from app.services.crypto.crypto import encryption -from app.services.crypto.crypto import generate_secret -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.crypto.crypto import decryption, encryption, generate_secret +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler class FilePermissions(IntEnum): diff --git a/app/configs/utils.py b/app/configs/utils.py index 265f61da..390ffe01 100644 --- a/app/configs/utils.py +++ b/app/configs/utils.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/models/__init__.py b/app/models/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/models/enums.py b/app/models/enums.py index 626c4be9..9eaa2c47 100644 --- a/app/models/enums.py +++ b/app/models/enums.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023-2025 Indoc Systems +# Copyright (C) 2023-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/models/item.py b/app/models/item.py index 0e9454e4..3edf04f1 100644 --- a/app/models/item.py +++ b/app/models/item.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023-2025 Indoc Systems +# Copyright (C) 2023-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/models/service_meta_class.py b/app/models/service_meta_class.py index 8c556bd0..c7b2b303 100644 --- a/app/models/service_meta_class.py +++ b/app/models/service_meta_class.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/models/singleton.py b/app/models/singleton.py index e24f7962..6f149bff 100644 --- a/app/models/singleton.py +++ b/app/models/singleton.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/models/upload_form.py b/app/models/upload_form.py index d259f0a2..5487d1d7 100644 --- a/app/models/upload_form.py +++ b/app/models/upload_form.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/pilotcli.py b/app/pilotcli.py index 8ebf5be3..eb6b3057 100644 --- a/app/pilotcli.py +++ b/app/pilotcli.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -11,12 +11,9 @@ import app.services.output_manager.error_handler as error_handler import app.services.output_manager.message_handler as mhandler -from app.commands.entry_point import command_groups -from app.commands.entry_point import entry_point +from app.commands.entry_point import command_groups, entry_point from app.services.output_manager.help_page import get_cli_help_message -from app.utils.aggregated import doc -from app.utils.aggregated import get_latest_cli_version -from app.utils.aggregated import get_version_compatibility +from app.utils.aggregated import doc, get_latest_cli_version, get_version_compatibility class CustomHelpFormatter(HelpFormatter): diff --git a/app/resources/custom_error.py b/app/resources/custom_error.py index 82eef5ba..e9a44483 100644 --- a/app/resources/custom_error.py +++ b/app/resources/custom_error.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/resources/custom_help.py b/app/resources/custom_help.py index 4e675ddd..2675baa6 100644 --- a/app/resources/custom_help.py +++ b/app/resources/custom_help.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/__init__.py b/app/services/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/__init__.py +++ b/app/services/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/clients/__init__.py b/app/services/clients/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/clients/__init__.py +++ b/app/services/clients/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/clients/base_auth_client.py b/app/services/clients/base_auth_client.py index 01c47b68..d2e84a6f 100644 --- a/app/services/clients/base_auth_client.py +++ b/app/services/clients/base_auth_client.py @@ -1,10 +1,9 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. import time -from typing import Any -from typing import Mapping +from typing import Any, Mapping from httpx import Response diff --git a/app/services/clients/base_client.py b/app/services/clients/base_client.py index 5cc23683..ac5f0efe 100644 --- a/app/services/clients/base_client.py +++ b/app/services/clients/base_client.py @@ -1,16 +1,12 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. import logging import time -from typing import Any -from typing import Mapping -from typing import Optional +from typing import Any, Mapping, Optional -from httpx import Client -from httpx import RequestError -from httpx import Response +from httpx import Client, RequestError, Response from app.configs.config import ConfigClass diff --git a/app/services/container_registry_manager/container_registry_manager.py b/app/services/container_registry_manager/container_registry_manager.py index af495d0f..7ce3efa3 100644 --- a/app/services/container_registry_manager/container_registry_manager.py +++ b/app/services/container_registry_manager/container_registry_manager.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -9,8 +9,7 @@ from app.configs.app_config import AppConfig from app.configs.user_config import UserConfig from app.models.service_meta_class import MetaService -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.services.user_authentication.decorator import require_valid_token diff --git a/app/services/crypto/__init__.py b/app/services/crypto/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/crypto/__init__.py +++ b/app/services/crypto/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/crypto/crypto.py b/app/services/crypto/crypto.py index f156f7c5..08014daf 100644 --- a/app/services/crypto/crypto.py +++ b/app/services/crypto/crypto.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/dataset_manager/dataset_detail.py b/app/services/dataset_manager/dataset_detail.py index 1e2cf3cd..012ce7b4 100644 --- a/app/services/dataset_manager/dataset_detail.py +++ b/app/services/dataset_manager/dataset_detail.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -9,8 +9,7 @@ from app.configs.user_config import UserConfig from app.models.service_meta_class import MetaService from app.services.clients.base_auth_client import BaseAuthClient -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from ..user_authentication.decorator import require_valid_token diff --git a/app/services/dataset_manager/dataset_download.py b/app/services/dataset_manager/dataset_download.py index 4aa3c26c..94d2dea0 100644 --- a/app/services/dataset_manager/dataset_download.py +++ b/app/services/dataset_manager/dataset_download.py @@ -1,12 +1,11 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. import datetime import os import time -from typing import Any -from typing import Dict +from typing import Any, Dict import httpx from httpx import HTTPStatusError @@ -18,8 +17,7 @@ from app.models.service_meta_class import MetaService from app.services.clients.base_auth_client import BaseAuthClient from app.services.dataset_manager.model import EFileStatus -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.services.output_manager.message_handler import SrvOutPutHandler from ..user_authentication.decorator import require_valid_token diff --git a/app/services/dataset_manager/dataset_list.py b/app/services/dataset_manager/dataset_list.py index db6f5f46..959cca90 100644 --- a/app/services/dataset_manager/dataset_list.py +++ b/app/services/dataset_manager/dataset_list.py @@ -1,11 +1,8 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. -from typing import Any -from typing import Dict -from typing import List -from typing import Tuple +from typing import Any, Dict, List, Tuple from httpx import HTTPStatusError @@ -13,8 +10,7 @@ from app.configs.user_config import UserConfig from app.models.service_meta_class import MetaService from app.services.clients.base_auth_client import BaseAuthClient -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.services.output_manager.message_handler import SrvOutPutHandler from ..user_authentication.decorator import require_valid_token diff --git a/app/services/dataset_manager/model.py b/app/services/dataset_manager/model.py index c50ece93..dd15fbb1 100644 --- a/app/services/dataset_manager/model.py +++ b/app/services/dataset_manager/model.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/file_manager/__init__.py b/app/services/file_manager/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/file_manager/__init__.py +++ b/app/services/file_manager/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/file_manager/file_download/__init__.py b/app/services/file_manager/file_download/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/file_manager/file_download/__init__.py +++ b/app/services/file_manager/file_download/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/file_manager/file_download/download_client.py b/app/services/file_manager/file_download/download_client.py index 02e9a151..bf4f0bd6 100644 --- a/app/services/file_manager/file_download/download_client.py +++ b/app/services/file_manager/file_download/download_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -19,8 +19,7 @@ from app.models.item import ItemZone from app.models.service_meta_class import MetaService from app.services.clients.base_auth_client import BaseAuthClient -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.services.user_authentication.decorator import require_valid_token from .model import EFileStatus @@ -54,12 +53,12 @@ def print_prepare_msg(self, message): finished_msg = message.replace('ing', 'ed') while True: if self.check_point: - click.secho(f"{finished_msg}{' '*space_width}\r", fg='white', nl=False) + click.secho(f"{finished_msg}{' ' * space_width}\r", fg='white', nl=False) break - click.secho(f"{message}{' '*space_width}\r", fg='white', nl=False) + click.secho(f"{message}{' ' * space_width}\r", fg='white', nl=False) for i in range(5): time.sleep(1) - click.secho(f"{message}{'.'*i}\r", fg='white', nl=False) + click.secho(f"{message}{'.' * i}\r", fg='white', nl=False) def get_download_url(self, zone): if zone == ItemZone.GREENROOM.value: diff --git a/app/services/file_manager/file_download/model.py b/app/services/file_manager/file_download/model.py index 87355185..6c750c14 100644 --- a/app/services/file_manager/file_download/model.py +++ b/app/services/file_manager/file_download/model.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/file_manager/file_list.py b/app/services/file_manager/file_list.py index 5cd0518d..3d44d39e 100644 --- a/app/services/file_manager/file_list.py +++ b/app/services/file_manager/file_list.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -10,12 +10,10 @@ import app.services.logger_services.log_functions as logger from app.configs.app_config import AppConfig -from app.models.item import ItemStatus -from app.models.item import ItemType +from app.models.item import ItemStatus, ItemType from app.models.service_meta_class import MetaService from app.services.clients.base_auth_client import BaseAuthClient -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.services.user_authentication.decorator import require_valid_token from app.utils.aggregated import fit_terminal_width diff --git a/app/services/file_manager/file_manifests.py b/app/services/file_manager/file_manifests.py index d014281e..b7a3907f 100644 --- a/app/services/file_manager/file_manifests.py +++ b/app/services/file_manager/file_manifests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -9,8 +9,7 @@ from app.configs.user_config import UserConfig from app.models.service_meta_class import MetaService from app.services.clients.base_auth_client import BaseAuthClient -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler def dupe_checking_hook(pairs): diff --git a/app/services/file_manager/file_metadata/__init__.py b/app/services/file_manager/file_metadata/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/file_manager/file_metadata/__init__.py +++ b/app/services/file_manager/file_metadata/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/file_manager/file_metadata/file_metadata_client.py b/app/services/file_manager/file_metadata/file_metadata_client.py index 977c02f8..2a5e6763 100644 --- a/app/services/file_manager/file_metadata/file_metadata_client.py +++ b/app/services/file_manager/file_metadata/file_metadata_client.py @@ -1,18 +1,12 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. import json +import sys from os import makedirs -from os.path import basename -from os.path import dirname -from os.path import exists -from os.path import join -from sys import exit -from typing import Any -from typing import Dict -from typing import List -from typing import Union +from os.path import basename, dirname, exists, join +from typing import Any, Dict, List, Union import click from click.exceptions import Abort @@ -20,10 +14,8 @@ import app.services.logger_services.log_functions as logger import app.services.output_manager.message_handler as message_handler from app.models.item import ItemType -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import customized_error_msg -from app.utils.aggregated import get_attribute_template_by_id -from app.utils.aggregated import search_item +from app.services.output_manager.error_handler import ECustomizedError, customized_error_msg +from app.utils.aggregated import get_attribute_template_by_id, search_item class FileMetaClient: @@ -87,7 +79,7 @@ def _check_duplication(self, general_loc: str, attribute_loc: str, tag_loc: str) click.confirm(duplicate_error, abort=True) except Abort: message_handler.SrvOutPutHandler.cancel_metadata_download() - exit(1) + sys.exit(1) def save_file_metadata(self, file_loc: str, metadata: Union[dict, list]) -> None: """ @@ -116,7 +108,7 @@ def download_file_metadata(self) -> List[Dict[str, Any]]: item_res = search_item(project_code, self.zone, object_path) if item_res.get('code') == 404: logger.error(f'Cannot find item {self.file_path} at {self.zone}.') - exit(1) + sys.exit(1) # filter out item metadata item_res = item_res.get('result', {}) diff --git a/app/services/file_manager/file_metadata/folder_client.py b/app/services/file_manager/file_metadata/folder_client.py index ef95a48b..8b145351 100644 --- a/app/services/file_manager/file_metadata/folder_client.py +++ b/app/services/file_manager/file_metadata/folder_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -10,8 +10,7 @@ from app.services.clients.base_auth_client import BaseAuthClient from app.services.output_manager import message_handler from app.services.output_manager.error_handler import SrvErrorHandler -from app.utils.aggregated import check_item_duplication -from app.utils.aggregated import search_item +from app.utils.aggregated import check_item_duplication, search_item class FolderClient(BaseAuthClient): diff --git a/app/services/file_manager/file_move/__init__.py b/app/services/file_manager/file_move/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/file_manager/file_move/__init__.py +++ b/app/services/file_manager/file_move/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/file_manager/file_move/file_move_client.py b/app/services/file_manager/file_move/file_move_client.py index c268dac1..f177e4e5 100644 --- a/app/services/file_manager/file_move/file_move_client.py +++ b/app/services/file_manager/file_move/file_move_client.py @@ -1,9 +1,9 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. +import sys import uuid -from sys import exit import click from click import Abort @@ -13,12 +13,9 @@ from app.configs.app_config import AppConfig from app.models.item import ItemType from app.services.clients.base_auth_client import BaseAuthClient -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler -from app.services.output_manager.error_handler import customized_error_msg +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler, customized_error_msg from app.services.user_authentication.decorator import require_valid_token -from app.utils.aggregated import check_item_duplication -from app.utils.aggregated import search_item +from app.utils.aggregated import check_item_duplication, search_item class FileMoveClient(BaseAuthClient): @@ -91,7 +88,7 @@ def create_object_path_if_not_exist(self, folder_path: str) -> dict: click.confirm(customized_error_msg(ECustomizedError.CREATE_FOLDER_IF_NOT_EXIST), abort=True) except Abort: message_handler.SrvOutPutHandler.move_cancelled() - exit(1) + sys.exit(1) else: return @@ -140,7 +137,7 @@ def move_file(self) -> None: message_handler.SrvOutPutHandler.move_action_failed( self.src_item_path, f'{self.dest_item_path}/{item_name}', f'Item {item_name} already exists' ) - exit(1) + sys.exit(1) else: self.dest_item_path = f'{self.dest_item_path}/{self.src_item_path.split("/")[-1]}' else: @@ -151,7 +148,7 @@ def move_file(self) -> None: message_handler.SrvOutPutHandler.move_action_failed( self.src_item_path, self.dest_item_path, f'Parent folder {parent_path} not exist' ) - exit(1) + sys.exit(1) try: payload = { @@ -174,4 +171,4 @@ def move_file(self) -> None: else: error_message = response.json().get('error_msg') message_handler.SrvOutPutHandler.move_action_failed(self.src_item_path, self.dest_item_path, error_message) - exit(1) + sys.exit(1) diff --git a/app/services/file_manager/file_tag.py b/app/services/file_manager/file_tag.py index 78277a0b..7b9f4e70 100644 --- a/app/services/file_manager/file_tag.py +++ b/app/services/file_manager/file_tag.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -9,9 +9,7 @@ from app.configs.app_config import AppConfig from app.configs.user_config import UserConfig from app.models.service_meta_class import MetaService -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler -from app.services.output_manager.error_handler import customized_error_msg +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler, customized_error_msg from app.services.user_authentication.decorator import require_valid_token diff --git a/app/services/file_manager/file_trash/__init__.py b/app/services/file_manager/file_trash/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/file_manager/file_trash/__init__.py +++ b/app/services/file_manager/file_trash/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/file_manager/file_trash/file_trash_client.py b/app/services/file_manager/file_trash/file_trash_client.py index b77d0d67..6c400265 100644 --- a/app/services/file_manager/file_trash/file_trash_client.py +++ b/app/services/file_manager/file_trash/file_trash_client.py @@ -1,11 +1,9 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. import time -from typing import Any -from typing import Dict -from typing import List +from typing import Any, Dict, List from uuid import UUID from httpx import HTTPStatusError @@ -14,10 +12,8 @@ from app.models.item import ItemStatus from app.services.clients.base_auth_client import BaseAuthClient from app.services.output_manager import message_handler -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler -from app.utils.aggregated import get_file_info_by_geid -from app.utils.aggregated import get_zone +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler +from app.utils.aggregated import get_file_info_by_geid, get_zone class FileTrashClient(BaseAuthClient): diff --git a/app/services/file_manager/file_trash/utils.py b/app/services/file_manager/file_trash/utils.py index 93bef481..512a4900 100644 --- a/app/services/file_manager/file_trash/utils.py +++ b/app/services/file_manager/file_trash/utils.py @@ -1,15 +1,11 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. -from typing import Dict -from typing import List -from typing import Tuple +from typing import Dict, List, Tuple -from app.models.item import ItemStatus -from app.models.item import ItemType -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.models.item import ItemStatus, ItemType +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.utils.aggregated import search_item diff --git a/app/services/file_manager/file_upload/__init__.py b/app/services/file_manager/file_upload/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/file_manager/file_upload/__init__.py +++ b/app/services/file_manager/file_upload/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/file_manager/file_upload/exception.py b/app/services/file_manager/file_upload/exception.py index c72f62ca..0d4df23b 100644 --- a/app/services/file_manager/file_upload/exception.py +++ b/app/services/file_manager/file_upload/exception.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -7,4 +7,6 @@ class INVALID_CHUNK_ETAG(Exception): chunk_number: int def __init__(self, chunk_number: int) -> None: + super().__init__(f'Invalid ETag for chunk number {chunk_number}') + self.chunk_number = chunk_number diff --git a/app/services/file_manager/file_upload/file_upload.py b/app/services/file_manager/file_upload/file_upload.py index 5f4f510d..708da93f 100644 --- a/app/services/file_manager/file_upload/file_upload.py +++ b/app/services/file_manager/file_upload/file_upload.py @@ -1,16 +1,14 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. import os +import sys import time import zipfile from multiprocessing.pool import ThreadPool -from sys import exit -from typing import Any -from typing import Dict -from typing import List -from typing import Tuple +from typing import Any, Dict, List, Tuple +from uuid import uuid4 import click from click.exceptions import Abort @@ -19,19 +17,11 @@ import app.services.output_manager.message_handler as mhandler from app.configs.app_config import AppConfig from app.models.item import ItemType -from app.services.file_manager.file_upload.models import FileObject -from app.services.file_manager.file_upload.models import ItemStatus -from app.services.file_manager.file_upload.models import UploadType +from app.services.file_manager.file_upload.models import FileObject, ItemStatus, UploadType from app.services.file_manager.file_upload.upload_client import UploadClient from app.services.logger_services.debugging_log import debug_logger -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler -from app.services.output_manager.error_handler import customized_error_msg -from app.utils.aggregated import batch_generator -from app.utils.aggregated import get_file_in_folder -from app.utils.aggregated import get_file_info_by_geid -from app.utils.aggregated import normalize_join -from app.utils.aggregated import search_item +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler, customized_error_msg +from app.utils.aggregated import batch_generator, get_file_in_folder, get_file_info_by_geid, normalize_join, search_item def compress_folder_to_zip(path: str) -> str: @@ -103,7 +93,7 @@ def assemble_path( click.confirm(customized_error_msg(ECustomizedError.CREATE_FOLDER_IF_NOT_EXIST), abort=True) except Abort: mhandler.SrvOutPutHandler.cancel_upload() - exit(1) + sys.exit(1) # stop scaning and use the current folder as parent folder current_folder_node = folder_path @@ -121,8 +111,8 @@ def assemble_path( def item_duplication_check( - create_folder_flag: bool, file_objects: List[FileObject], upload_client: UploadClient -) -> List[FileObject]: + create_folder_flag: bool, file_objects: List[FileObject], upload_client: UploadClient, on_resume: bool = False +) -> Tuple[List[FileObject], List[Dict[str, Any]]]: ''' Summary: The function will check if the file is already uploaded on the platform. @@ -133,28 +123,40 @@ def item_duplication_check( - file_objects(List[FileObject]): the list of file object - upload_client(UploadClient): the upload client object Return: - - non_duplicate_file_objects(List[FileObject]): the list of file object that is not duplicated + - unregistered_items(List[FileObject]): the list of file object that is not duplicated + - [updated] registered_items(List[FileObject]): the list of file object that is already registered. + in conner case if preupload interrupted at specific batch, the local manifest + will mismatch with backend metadata. So we need to return the registered file objects as well. ''' # make the file duplication check to allow folde merging logger.info('Start checking file duplication') - non_duplicate_file_objects = [] + unregistered_items, registered_items = [], [] if create_folder_flag is True: - non_duplicate_file_objects = file_objects + unregistered_items = file_objects else: mhandler.SrvOutPutHandler.file_duplication_check() duplicated_file = [] debug_logger.debug(f'upload batch size: {AppConfig.Env.upload_batch_size}') for file_batchs in batch_generator(file_objects, batch_size=AppConfig.Env.upload_batch_size): start_time = time.time() - non_duplicates, duplicate_path = upload_client.check_upload_duplication(file_batchs) + non_duplicates, active_paths, registered_batch_items = upload_client.check_upload_duplication(file_batchs) debug_logger.debug(f'Check duplication time: {time.time() - start_time:.2f}s') - non_duplicate_file_objects.extend(non_duplicates) - duplicated_file.extend(duplicate_path) + unregistered_items.extend(non_duplicates) + duplicated_file.extend(active_paths) + registered_items.extend(registered_batch_items) + + # if normal upload we treat REGISTERED file as duplication + if not on_resume: + object_path = [ + file_object.get('parent_path', '') + '/' + file_object.get('name', '') + for file_object in registered_items + ] + duplicated_file.extend(object_path) # if all file objects we check are already existed on the platform # then we will exit the upload process - if len(non_duplicate_file_objects) == 0 and len(file_objects) != 0: + if len(unregistered_items) == 0 and len(file_objects) != 0 and on_resume is False: mhandler.SrvOutPutHandler.file_duplication_check_warning_with_all_same() SrvErrorHandler.customized_handle(ECustomizedError.UPLOAD_CANCEL, if_exit=True) elif len(duplicated_file) > 0: @@ -167,9 +169,9 @@ def item_duplication_check( ) except Abort: mhandler.SrvOutPutHandler.cancel_upload() - exit(1) + sys.exit(1) - return non_duplicate_file_objects + return unregistered_items, registered_items def simple_upload( # noqa: C901 @@ -246,7 +248,7 @@ def simple_upload( # noqa: C901 file_objects.append(file_object) # make the file duplication check to allow folder merging - non_duplicate_file_objects = item_duplication_check(create_folder_flag, file_objects, upload_client) + non_duplicate_file_objects, _ = item_duplication_check(create_folder_flag, file_objects, upload_client) # here is list of pre upload result. We decided to call pre upload api by batch pre_upload_infos = [] @@ -393,7 +395,7 @@ def resume_upload( logger.info(f'Registered items: {len(unfinished_items)}') # make the file duplication check to allow folder merging - unregistered_items = manifest_json.get('unregistered_items') + unregistered_items_map = manifest_json.get('unregistered_items') unregistered_items = [ FileObject( object_path=value.get('object_path'), @@ -402,18 +404,46 @@ def resume_upload( job_id=value.get('job_id'), item_id=value.get('item_id'), ) - for _, value in unregistered_items.items() + for _, value in unregistered_items_map.items() ] - unregistered_items = item_duplication_check(False, unregistered_items, upload_client) + + # [updated] here duplication check api got update will filter out ACTIVE and REGISTERED items separately + # the reason is during the normal upload , there is a conner case that preupload got interrupted at + # specific batch so the local manifest will mismatch with backend metadata. Thus we need to return + # the registered file objects as well. + unregistered_items, unmatched_items = item_duplication_check( + False, unregistered_items, upload_client, on_resume=True + ) + # now create FileObject with local path + for item in unmatched_items: + object_path = item.get('parent_path', '') + '/' + item.get('name', '') + local_path = ( + unregistered_items_map.get(object_path).get('local_path') if unregistered_items_map.get(object_path) else '' + ) + if not local_path: + logger.warning(f'Cannot find local path for registered item: {object_path}. Skip it.') + continue + + registered_file_object = FileObject( + object_path=object_path, + local_path=local_path, + item_id=item.get('id'), + resumable_id=item.get('upload_id'), + job_id=str(uuid4()), + ) + unfinished_items.append(registered_file_object) + + # also need to update local manifest to remove the duplicated files + resumable_manifest_file = manifest_json.get('resumable_manifest_file') + upload_client.output_manifest(unfinished_items, unregistered_items, resumable_manifest_file) logger.info(f'Unregistered items: {len(unregistered_items)}') # redo preupload again - batch_count = 1 - resumable_manifest_file = manifest_json.get('resumable_manifest_file') + processed_count = 0 for file_batchs in batch_generator(unregistered_items, batch_size=AppConfig.Env.upload_batch_size): unfinished_items.extend(upload_client.pre_upload(file_batchs)) - upload_client.output_manifest(unfinished_items, unregistered_items[batch_count + 1 :], resumable_manifest_file) - batch_count += 1 + processed_count += len(file_batchs) + upload_client.output_manifest(unfinished_items, unregistered_items[processed_count:], resumable_manifest_file) mhandler.SrvOutPutHandler.resume_warning(len(unfinished_items)) mhandler.SrvOutPutHandler.resume_check_success() diff --git a/app/services/file_manager/file_upload/models.py b/app/services/file_manager/file_upload/models.py index f2a069b1..4b3ff837 100644 --- a/app/services/file_manager/file_upload/models.py +++ b/app/services/file_manager/file_upload/models.py @@ -1,15 +1,11 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. import math from enum import Enum -from os.path import basename -from os.path import dirname -from os.path import getsize -from typing import Any -from typing import Dict -from typing import Tuple +from os.path import basename, dirname, getsize +from typing import Any, Dict, Tuple from tqdm import tqdm diff --git a/app/services/file_manager/file_upload/upload_client.py b/app/services/file_manager/file_upload/upload_client.py index 704bf1ae..e225b375 100644 --- a/app/services/file_manager/file_upload/upload_client.py +++ b/app/services/file_manager/file_upload/upload_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -11,10 +11,7 @@ import time from logging import getLogger from multiprocessing.pool import ThreadPool -from typing import Any -from typing import Dict -from typing import List -from typing import Tuple +from typing import Any, Dict, List, Tuple from uuid import UUID import httpx @@ -25,14 +22,10 @@ from app.configs.user_config import UserConfig from app.models.upload_form import generate_on_success_form from app.services.clients.base_auth_client import BaseAuthClient -from app.services.file_manager.file_upload.models import FileObject -from app.services.file_manager.file_upload.models import UploadType -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.file_manager.file_upload.models import FileObject, UploadType +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.services.user_authentication.decorator import require_valid_token -from app.utils.aggregated import ItemStatus -from app.utils.aggregated import batch_generator -from app.utils.aggregated import get_file_info_by_geid +from app.utils.aggregated import ItemStatus, batch_generator, get_file_info_by_geid from .exception import INVALID_CHUNK_ETAG @@ -158,7 +151,9 @@ def resume_upload(self, unfinished_file_objects: List[FileObject]) -> List[FileO return unfinished_file_objects @require_valid_token() - def check_upload_duplication(self, file_objects: List[FileObject]) -> Tuple[List[FileObject], List[str]]: + def check_upload_duplication( + self, file_objects: List[FileObject] + ) -> Tuple[List[FileObject], List[str], List[Dict[str, Any]]]: """ Summary: The function will call the api to check if the file has been uploaded. @@ -168,6 +163,9 @@ def check_upload_duplication(self, file_objects: List[FileObject]) -> Tuple[List return: - non_exist_file_objects(List[FileObject]): the file that need to be uploaded. - exist_files(List[str]): the file that has been uploaded. will be skipped + - [updated] registered_file_objects(List[Dict[str, Any]]): this is to handle the conner case + where upload interrupted at specific batch. The local json manifest mismatches with + backend metadata. So we need to return the registered file objects as well if possible. """ # generate a list of locations for uploaded files to check duplication @@ -182,8 +180,8 @@ def check_upload_duplication(self, file_objects: List[FileObject]) -> Tuple[List 'zone': 0 if self.zone == 'greenroom' else 1, } try: - self.endpoint = AppConfig.Connections.url_base + '/portal/v1' - response = self._post('files/exists', json=payload) + self.endpoint = AppConfig.Connections.url_bff + '/v2' + response = self._post('items/batch/exists', json=payload) except HTTPStatusError as e: response = e.response if response.status_code == 403: @@ -191,11 +189,16 @@ def check_upload_duplication(self, file_objects: List[FileObject]) -> Tuple[List else: SrvErrorHandler.default_handle('Error when checking file duplication', True) - # pop the file object if the file has been uploaded - # return the file objects that need to be uploaded - exist_files = response.json().get('result', []) - for exist_file_path in exist_files: - object_path_file_object_map.pop(exist_file_path.lower()) + # filter out the ACTIVE items and REGISTERED items from return + exist_items = response.json().get('result', []) + active_path, registered_items = [], [] + for item in exist_items: + object_path = item.get('parent_path', '') + '/' + item.get('name', '') + object_path_file_object_map.pop(object_path.lower(), None) + if item.get('status') == ItemStatus.ACTIVE: + active_path.append(object_path) + else: + registered_items.append(item) # reconstruct non exist file objects which will be uploaded # without lower() function. @@ -203,7 +206,7 @@ def check_upload_duplication(self, file_objects: List[FileObject]) -> Tuple[List for _, item in object_path_file_object_map.items(): return_list.update({item.object_path: item}) - return list(object_path_file_object_map.values()), exist_files + return list(object_path_file_object_map.values()), active_path, registered_items @require_valid_token() def pre_upload(self, file_objects: List[FileObject]) -> List[FileObject]: @@ -294,7 +297,9 @@ def output_manifest( 'current_folder_node': self.current_folder_node, 'tags': self.tags, 'registered_items': {file_object.item_id: file_object.to_dict() for file_object in registered_items}, - 'unregistered_items': {file_object.local_path: file_object.to_dict() for file_object in unregistered_items}, + 'unregistered_items': { + file_object.object_path: file_object.to_dict() for file_object in unregistered_items + }, 'attributes': self.attributes if self.attributes else {}, 'resumable_manifest_file': output_path, } diff --git a/app/services/file_manager/file_upload/upload_validator.py b/app/services/file_manager/file_upload/upload_validator.py index 5bbee8be..15fd663c 100644 --- a/app/services/file_manager/file_upload/upload_validator.py +++ b/app/services/file_manager/file_upload/upload_validator.py @@ -1,15 +1,12 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. -from typing import Any -from typing import Dict -from typing import List +from typing import Any, Dict, List from app.services.file_manager.file_manifests import SrvFileManifests from app.services.file_manager.file_tag import SrvFileTag -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.utils.aggregated import search_item diff --git a/app/services/logger_services/__init__.py b/app/services/logger_services/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/logger_services/__init__.py +++ b/app/services/logger_services/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/logger_services/debugging_log.py b/app/services/logger_services/debugging_log.py index 872b9dd8..e6973d64 100644 --- a/app/services/logger_services/debugging_log.py +++ b/app/services/logger_services/debugging_log.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/logger_services/log_functions.py b/app/services/logger_services/log_functions.py index 1f150de7..50ec6771 100644 --- a/app/services/logger_services/log_functions.py +++ b/app/services/logger_services/log_functions.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/output_manager/__init__.py b/app/services/output_manager/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/output_manager/__init__.py +++ b/app/services/output_manager/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/output_manager/error_handler.py b/app/services/output_manager/error_handler.py index f9f9da8a..c5f21a8a 100644 --- a/app/services/output_manager/error_handler.py +++ b/app/services/output_manager/error_handler.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/output_manager/help_page.py b/app/services/output_manager/help_page.py index 88911550..cf7ae4e1 100644 --- a/app/services/output_manager/help_page.py +++ b/app/services/output_manager/help_page.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/output_manager/message_handler.py b/app/services/output_manager/message_handler.py index 73148c0a..c8fb9783 100644 --- a/app/services/output_manager/message_handler.py +++ b/app/services/output_manager/message_handler.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/project_manager/__init__.py b/app/services/project_manager/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/project_manager/__init__.py +++ b/app/services/project_manager/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/project_manager/project.py b/app/services/project_manager/project.py index 237b15bd..e84b40bc 100644 --- a/app/services/project_manager/project.py +++ b/app/services/project_manager/project.py @@ -1,16 +1,14 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. -from typing import Dict -from typing import Tuple +from typing import Dict, Tuple from app.configs.app_config import AppConfig from app.configs.user_config import UserConfig from app.models.service_meta_class import MetaService from app.services.clients.base_auth_client import BaseAuthClient -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.services.output_manager.message_handler import SrvOutPutHandler from ..user_authentication.decorator import require_valid_token diff --git a/app/services/user_authentication/__init__.py b/app/services/user_authentication/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/services/user_authentication/__init__.py +++ b/app/services/user_authentication/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/services/user_authentication/decorator.py b/app/services/user_authentication/decorator.py index ab0562f6..7ed365b1 100644 --- a/app/services/user_authentication/decorator.py +++ b/app/services/user_authentication/decorator.py @@ -1,15 +1,13 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. from functools import wraps from app.configs.config import ConfigClass -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.services.user_authentication.token_manager import SrvTokenManager -from app.services.user_authentication.user_login_logout import check_is_active -from app.services.user_authentication.user_login_logout import check_is_login +from app.services.user_authentication.user_login_logout import check_is_active, check_is_login def require_valid_token(azp=ConfigClass.keycloak_device_client_id): diff --git a/app/services/user_authentication/token_manager.py b/app/services/user_authentication/token_manager.py index 8340456c..7fe6bdcd 100644 --- a/app/services/user_authentication/token_manager.py +++ b/app/services/user_authentication/token_manager.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -12,8 +12,7 @@ from app.configs.user_config import UserConfig from app.models.service_meta_class import MetaService from app.services.clients.base_client import BaseClient -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.services.user_authentication.user_login_logout import login_using_api_key diff --git a/app/services/user_authentication/user_login_logout.py b/app/services/user_authentication/user_login_logout.py index 15a69af7..81d68ed5 100644 --- a/app/services/user_authentication/user_login_logout.py +++ b/app/services/user_authentication/user_login_logout.py @@ -1,12 +1,9 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. import time -from typing import Any -from typing import Dict -from typing import Tuple -from typing import Union +from typing import Any, Dict, Tuple, Union from uuid import uuid4 import jwt @@ -16,8 +13,7 @@ from app.configs.config import ConfigClass from app.configs.user_config import UserConfig from app.services.clients.base_client import BaseClient -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.services.output_manager.message_handler import SrvOutPutHandler diff --git a/app/utils/__init__.py b/app/utils/__init__.py index 50fc3895..2dadda1a 100644 --- a/app/utils/__init__.py +++ b/app/utils/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/app/utils/aggregated.py b/app/utils/aggregated.py index bc33680f..456777b0 100644 --- a/app/utils/aggregated.py +++ b/app/utils/aggregated.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -7,23 +7,17 @@ import re import shutil import time -from typing import Any -from typing import Dict -from typing import List -from typing import Tuple +from typing import Any, Dict, List, Tuple from httpx import HTTPStatusError from packaging.version import Version import app.services.logger_services.log_functions as logger from app.configs.app_config import AppConfig -from app.models.item import ItemStatus -from app.models.item import ItemType -from app.services.clients.base_auth_client import BaseAuthClient -from app.services.clients.base_auth_client import BaseClient +from app.models.item import ItemStatus, ItemType +from app.services.clients.base_auth_client import BaseAuthClient, BaseClient from app.services.logger_services.debugging_log import debug_logger -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import SrvErrorHandler +from app.services.output_manager.error_handler import ECustomizedError, SrvErrorHandler from app.services.user_authentication.decorator import require_valid_token diff --git a/docs/compatible_version.ndjson b/docs/compatible_version.ndjson index 7bddc8da..55e1bb82 100644 --- a/docs/compatible_version.ndjson +++ b/docs/compatible_version.ndjson @@ -16,3 +16,4 @@ {"Cli Version": "3.19.7", "Pilot Release Version": "2.15.2", "Compatible Version": "2.15.2"} {"Cli Version": "3.20.0", "Pilot Release Version": "2.16.0", "Compatible Version": "2.15.2"} {"Cli Version": "3.20.1", "Pilot Release Version": "2.16.0", "Compatible Version": "2.16.0"} +{"Cli Version": "3.20.1", "Pilot Release Version": "Unknown", "Compatible Version": "Unknown"} diff --git a/poetry.lock b/poetry.lock index ecc3ccb6..cec63733 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "altgraph" @@ -6,6 +6,7 @@ version = "0.17.4" description = "Python graph (network) package" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"}, {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, @@ -17,6 +18,7 @@ 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"}, @@ -28,6 +30,7 @@ version = "4.6.2.post1" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, @@ -41,7 +44,7 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (>=0.26.1)"] [[package]] @@ -50,6 +53,8 @@ version = "1.4.1" description = "Atomic file writes." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main", "dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, ] @@ -60,18 +65,19 @@ version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "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.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\""] [[package]] name = "certifi" @@ -79,6 +85,7 @@ version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, @@ -90,6 +97,7 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main"] 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"}, @@ -169,6 +177,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -180,6 +189,7 @@ version = "3.4.2" 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.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, @@ -281,6 +291,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -295,10 +306,12 @@ 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"] 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\" or sys_platform == \"win32\"", dev = "sys_platform == \"win32\""} [[package]] name = "coverage" @@ -306,6 +319,7 @@ version = "7.6.4" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, @@ -375,7 +389,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cryptography" @@ -383,6 +397,7 @@ version = "3.4.8" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "cryptography-3.4.8-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14"}, {file = "cryptography-3.4.8-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"}, @@ -422,6 +437,7 @@ version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -433,6 +449,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +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"}, @@ -447,6 +465,7 @@ version = "18.9.0" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "Faker-18.9.0-py3-none-any.whl", hash = "sha256:defe9ed618a67ebf0f3eb1895e198c2355a7128a09087a6dce342ef2253263ea"}, {file = "Faker-18.9.0.tar.gz", hash = "sha256:80a5ea1464556c06b98bf47ea3adc7f33811a1182518d847860b1874080bd3c9"}, @@ -461,6 +480,7 @@ version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, @@ -469,7 +489,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] -typing = ["typing-extensions (>=4.12.2)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "h11" @@ -477,6 +497,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -488,6 +509,7 @@ version = "0.16.3" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, @@ -509,6 +531,7 @@ version = "0.23.3" description = "The next generation HTTP client." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, @@ -521,7 +544,7 @@ rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} sniffio = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -532,6 +555,7 @@ version = "2.6.1" description = "File identification library for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, @@ -546,6 +570,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -560,6 +585,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -571,6 +597,8 @@ version = "1.16.3" description = "Mach-O header analysis and editing" optional = false python-versions = "*" +groups = ["main"] +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"}, @@ -585,6 +613,7 @@ version = "1.9.1" 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 = ["main"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -596,6 +625,7 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -607,6 +637,8 @@ version = "2023.2.7" description = "Python PE parsing module" optional = false python-versions = ">=3.6.0" +groups = ["main"] +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"}, @@ -618,6 +650,7 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -634,6 +667,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -649,6 +683,7 @@ version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, @@ -667,6 +702,7 @@ version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, @@ -681,6 +717,7 @@ version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main", "dev"] files = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, @@ -692,6 +729,7 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -703,6 +741,7 @@ version = "2.9.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, @@ -715,7 +754,7 @@ typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and sys_platform == \"win32\""] [[package]] name = "pydantic-core" @@ -723,6 +762,7 @@ version = "2.23.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, @@ -824,6 +864,7 @@ version = "2.0.1" description = "Settings management using Pydantic" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "pydantic_settings-2.0.1-py3-none-any.whl", hash = "sha256:579bbcbec3501e62bab73867b097ae10218201950e897463c98a182ffe7ed104"}, {file = "pydantic_settings-2.0.1.tar.gz", hash = "sha256:f440ec7cfb6dc63f03226c47b0e7803750d1b66a49ed944ac23eb4f0c84f8722"}, @@ -839,6 +880,7 @@ version = "6.11.1" description = "PyInstaller bundles a Python application and all its dependencies into a single package." optional = false python-versions = "<3.14,>=3.8" +groups = ["main"] files = [ {file = "pyinstaller-6.11.1-py3-none-macosx_10_13_universal2.whl", hash = "sha256:44e36172de326af6d4e7663b12f71dbd34e2e3e02233e181e457394423daaf03"}, {file = "pyinstaller-6.11.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:6d12c45a29add78039066a53fb05967afaa09a672426072b13816fe7676abfc4"}, @@ -873,6 +915,7 @@ version = "2025.1" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pyinstaller_hooks_contrib-2025.1-py3-none-any.whl", hash = "sha256:d3c799470cbc0bda60dcc8e6b4ab976777532b77621337f2037f558905e3a8e9"}, {file = "pyinstaller_hooks_contrib-2025.1.tar.gz", hash = "sha256:130818f9e9a0a7f2261f1fd66054966a3a50c99d000981c5d1db11d3ad0c6ab2"}, @@ -888,6 +931,7 @@ version = "2.9.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, @@ -905,6 +949,7 @@ 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"}, @@ -916,6 +961,7 @@ version = "6.2.5" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, @@ -940,6 +986,7 @@ version = "1.1.0" description = "Pytest plugin for Click" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pytest_click-1.1.0-py3-none-any.whl", hash = "sha256:eade4742c2f02c345e78a32534a43e8db04acf98d415090539dacc880b7cd0e9"}, {file = "pytest_click-1.1.0.tar.gz", hash = "sha256:fdd9f6721f877dda021e7c5dc73e70aecd37e5ed23ec6820f8a7b3fd7b4f8d30"}, @@ -955,6 +1002,7 @@ version = "3.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, @@ -973,6 +1021,7 @@ version = "0.21.3" description = "Send responses to httpx." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest_httpx-0.21.3-py3-none-any.whl", hash = "sha256:50b52b910f6f6cfb0aa65039d6f5bedb6ae3a0c02a98c4a7187543fe437c428a"}, {file = "pytest_httpx-0.21.3.tar.gz", hash = "sha256:edcb62baceffbd57753c1a7afc4656b0e71e91c7a512e143c0adbac762d979c1"}, @@ -991,6 +1040,7 @@ version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" +groups = ["dev"] 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"}, @@ -1008,6 +1058,7 @@ version = "1.1.0" description = "Randomise the order in which pytest tests are run with some control over the randomness" optional = false python-versions = ">=3.5.0" +groups = ["dev"] files = [ {file = "pytest-random-order-1.1.0.tar.gz", hash = "sha256:dbe6debb9353a7af984cc9eddbeb3577dd4dbbcc1529a79e3d21f68ed9b45605"}, {file = "pytest_random_order-1.1.0-py3-none-any.whl", hash = "sha256:6cb1e59ab0f798bb0c3488c11ae0c70d7d3340306a466d28b28ccd8ef8c20b7e"}, @@ -1022,6 +1073,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -1036,6 +1088,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -1050,6 +1103,8 @@ version = "306" description = "Python for Window Extensions" optional = true python-versions = "*" +groups = ["main"] +markers = "extra == \"windows\"" files = [ {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, @@ -1073,6 +1128,8 @@ version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" +groups = ["main"] +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"}, @@ -1084,6 +1141,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main"] 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"}, @@ -1146,6 +1204,7 @@ 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"}, @@ -1169,6 +1228,7 @@ version = "1.10.0" description = "Python library to build pretty command line user prompts ⭐️" optional = false python-versions = ">=3.6,<4.0" +groups = ["main"] files = [ {file = "questionary-1.10.0-py3-none-any.whl", hash = "sha256:fecfcc8cca110fda9d561cb83f1e97ecbb93c613ff857f655818839dac74ce90"}, {file = "questionary-1.10.0.tar.gz", hash = "sha256:600d3aefecce26d48d97eee936fdb66e4bc27f934c3ab6dd1e292c4f43946d90"}, @@ -1186,6 +1246,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -1207,6 +1268,7 @@ version = "1.12.1" description = "Mock out responses from the requests package" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"}, {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, @@ -1224,6 +1286,7 @@ version = "1.5.0" description = "Validating URI References per RFC 3986" optional = false python-versions = "*" +groups = ["main", "dev"] files = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, @@ -1241,19 +1304,20 @@ version = "75.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] -core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +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.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "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)", "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", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] +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"] [[package]] name = "six" @@ -1261,6 +1325,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["dev"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1272,6 +1337,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -1283,6 +1349,7 @@ version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main", "dev"] files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -1294,6 +1361,8 @@ version = "2.0.2" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, @@ -1305,6 +1374,7 @@ version = "4.56.0" description = "Fast, Extensible Progress Meter" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["main"] files = [ {file = "tqdm-4.56.0-py2.py3-none-any.whl", hash = "sha256:4621f6823bab46a9cc33d48105753ccbea671b68bab2c50a9f0be23d4065cb5a"}, {file = "tqdm-4.56.0.tar.gz", hash = "sha256:fe3d08dd00a526850568d542ff9de9bbc2a09a791da3c334f3213d8d0bbbca65"}, @@ -1320,10 +1390,12 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +markers = {dev = "python_version == \"3.10\""} [[package]] name = "urllib3" @@ -1331,13 +1403,14 @@ version = "2.3.0" 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.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1348,6 +1421,7 @@ version = "20.27.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "virtualenv-20.27.0-py3-none-any.whl", hash = "sha256:44a72c29cceb0ee08f300b314848c86e57bf8d1f13107a5e671fb9274138d655"}, {file = "virtualenv-20.27.0.tar.gz", hash = "sha256:2ca56a68ed615b8fe4326d11a0dca5dfbe8fd68510fb6c6349163bed3c15f2b2"}, @@ -1360,7 +1434,7 @@ platformdirs = ">=3.9.1,<5" [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)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +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 == \"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\""] [[package]] name = "wcwidth" @@ -1368,6 +1442,7 @@ version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, @@ -1377,6 +1452,6 @@ files = [ windows = ["pywin32"] [metadata] -lock-version = "2.0" -python-versions = ">=3.10,<3.11" -content-hash = "8743fc1b849d2f7b34660a2a575b06b895f13437b46c2beba2caf42ed694a11d" +lock-version = "2.1" +python-versions = ">=3.10,<3.13" +content-hash = "8697b730868cdb6b9cd147bd14486aca4ff7d612428401f080fc7c27b5473609" diff --git a/pyproject.toml b/pyproject.toml index 69d95428..3effe3f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [tool.poetry] name = "app" -version = "3.20.1" +version = "3.21.0" description = "This service is designed to support pilot platform" authors = ["Indoc Systems"] [tool.poetry.dependencies] -python = ">=3.10,<3.11" +python = ">=3.10,<3.13" questionary = "^1.10.0" tqdm = "4.56.0" cryptography = "3.4.8" diff --git a/tests/__init__.py b/tests/__init__.py index 50fc3895..2dadda1a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/commands/test_dataset.py b/tests/app/commands/test_dataset.py index 09f46eda..15f39ec7 100644 --- a/tests/app/commands/test_dataset.py +++ b/tests/app/commands/test_dataset.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -7,9 +7,7 @@ import pytest import questionary -from app.commands.dataset import dataset_download -from app.commands.dataset import dataset_list -from app.commands.dataset import dataset_show_detail +from app.commands.dataset import dataset_download, dataset_list, dataset_show_detail from app.configs.app_config import AppConfig diff --git a/tests/app/commands/test_entry_point.py b/tests/app/commands/test_entry_point.py index 19fb5985..c42c17cf 100644 --- a/tests/app/commands/test_entry_point.py +++ b/tests/app/commands/test_entry_point.py @@ -1,24 +1,22 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. -from app.commands.dataset import dataset_download -from app.commands.dataset import dataset_list -from app.commands.dataset import dataset_show_detail -from app.commands.entry_point import command_groups -from app.commands.entry_point import entry_point -from app.commands.file import file_check_manifest -from app.commands.file import file_download -from app.commands.file import file_export_manifest -from app.commands.file import file_list -from app.commands.file import file_metadata_download -from app.commands.file import file_move -from app.commands.file import file_put -from app.commands.file import file_resume -from app.commands.file import file_trash +from app.commands.dataset import dataset_download, dataset_list, dataset_show_detail +from app.commands.entry_point import command_groups, entry_point +from app.commands.file import ( + file_check_manifest, + file_download, + file_export_manifest, + file_list, + file_metadata_download, + file_move, + file_put, + file_resume, + file_trash, +) from app.commands.project import project_list_all -from app.commands.user import login -from app.commands.user import logout +from app.commands.user import login, logout def test_entry_point(): diff --git a/tests/app/commands/test_file.py b/tests/app/commands/test_file.py index 00e2455c..02a6ccde 100644 --- a/tests/app/commands/test_file.py +++ b/tests/app/commands/test_file.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -11,19 +11,20 @@ import pytest import questionary -from app.commands.file import file_download -from app.commands.file import file_list -from app.commands.file import file_metadata_download -from app.commands.file import file_move -from app.commands.file import file_put -from app.commands.file import file_resume -from app.commands.file import file_trash +from app.commands.file import ( + file_download, + file_list, + file_metadata_download, + file_move, + file_put, + file_resume, + file_trash, +) from app.configs.app_config import AppConfig from app.models.item import ItemType from app.services.file_manager.file_metadata.file_metadata_client import FileMetaClient from app.services.file_manager.file_upload.models import FileObject -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import customized_error_msg +from app.services.output_manager.error_handler import ECustomizedError, customized_error_msg from tests.conftest import decoded_token diff --git a/tests/app/commands/test_folder.py b/tests/app/commands/test_folder.py index 7a1471cf..791f2dba 100644 --- a/tests/app/commands/test_folder.py +++ b/tests/app/commands/test_folder.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/commands/test_project_command.py b/tests/app/commands/test_project_command.py index e4b54d2e..c7a99455 100644 --- a/tests/app/commands/test_project_command.py +++ b/tests/app/commands/test_project_command.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/commands/test_user.py b/tests/app/commands/test_user.py index d4af0b49..09d4cd3f 100644 --- a/tests/app/commands/test_user.py +++ b/tests/app/commands/test_user.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023-2025 Indoc Systems +# Copyright (C) 2023-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -108,7 +108,7 @@ def test_login_command_with_newer_version_available_message( result = cli_runner.invoke(login) assert result.exit_code == 0 - assert login_using_api_key_mock.called_once_with(api_key) + login_using_api_key_mock.assert_called_once_with(api_key) if Version(current_version) < Version(new_version): except_message = mhandler.SrvOutPutHandler.newer_version_available( new_version, download_url, print_message=False diff --git a/tests/app/configs/__init__.py b/tests/app/configs/__init__.py index 0886395d..c7720cf6 100644 --- a/tests/app/configs/__init__.py +++ b/tests/app/configs/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2023-2025 Indoc Systems +# Copyright (C) 2023-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/configs/test_user_config.py b/tests/app/configs/test_user_config.py index c55254c3..0d32fe2f 100644 --- a/tests/app/configs/test_user_config.py +++ b/tests/app/configs/test_user_config.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023-2025 Indoc Systems +# Copyright (C) 2023-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -8,8 +8,7 @@ import pytest -from app.configs.user_config import FilePermissions -from app.configs.user_config import UserConfig +from app.configs.user_config import FilePermissions, UserConfig @pytest.fixture diff --git a/tests/app/services/dataset_manager/test_dataset_detail.py b/tests/app/services/dataset_manager/test_dataset_detail.py index e597d9d2..0b640dcc 100644 --- a/tests/app/services/dataset_manager/test_dataset_detail.py +++ b/tests/app/services/dataset_manager/test_dataset_detail.py @@ -1,12 +1,11 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. import pytest from app.services.dataset_manager.dataset_detail import SrvDatasetDetailManager -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import customized_error_msg +from app.services.output_manager.error_handler import ECustomizedError, customized_error_msg test_dataset_code = 'test_code' diff --git a/tests/app/services/dataset_manager/test_dataset_download.py b/tests/app/services/dataset_manager/test_dataset_download.py index 7f655818..354960d1 100644 --- a/tests/app/services/dataset_manager/test_dataset_download.py +++ b/tests/app/services/dataset_manager/test_dataset_download.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/services/dataset_manager/test_dataset_list.py b/tests/app/services/dataset_manager/test_dataset_list.py index 1c0c8ef1..36d3fe10 100644 --- a/tests/app/services/dataset_manager/test_dataset_list.py +++ b/tests/app/services/dataset_manager/test_dataset_list.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/services/file_manager/file_download/test_file_download_client.py b/tests/app/services/file_manager/file_download/test_file_download_client.py index 58bb22ab..233c229f 100644 --- a/tests/app/services/file_manager/file_download/test_file_download_client.py +++ b/tests/app/services/file_manager/file_download/test_file_download_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -14,8 +14,7 @@ from app.models.item import ItemZone from app.services.file_manager.file_download.download_client import SrvFileDownload from app.services.file_manager.file_download.model import EFileStatus -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import customized_error_msg +from app.services.output_manager.error_handler import ECustomizedError, customized_error_msg from tests.conftest import decoded_token @@ -370,7 +369,7 @@ def test_base_client_timeout_logs_error_type(mocker): mock_logger = mocker.patch('app.services.clients.base_client.logger.error') - with pytest.raises(Exception): + with pytest.raises(Exception, match=r'Unable to query data.*ReadTimeout'): test_client.download_status() mock_logger.assert_called_once() diff --git a/tests/app/services/file_manager/file_metadata/test_file_metadata_client.py b/tests/app/services/file_manager/file_metadata/test_file_metadata_client.py index fc056e5e..a54624a2 100644 --- a/tests/app/services/file_manager/file_metadata/test_file_metadata_client.py +++ b/tests/app/services/file_manager/file_metadata/test_file_metadata_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/services/file_manager/file_metadata/test_folder_client.py b/tests/app/services/file_manager/file_metadata/test_folder_client.py index f4d862db..23c13cda 100644 --- a/tests/app/services/file_manager/file_metadata/test_folder_client.py +++ b/tests/app/services/file_manager/file_metadata/test_folder_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/services/file_manager/file_move/test_file_move_client.py b/tests/app/services/file_manager/file_move/test_file_move_client.py index 30c0e91f..b7032a38 100644 --- a/tests/app/services/file_manager/file_move/test_file_move_client.py +++ b/tests/app/services/file_manager/file_move/test_file_move_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/services/file_manager/file_trash/test_file_trash_client.py b/tests/app/services/file_manager/file_trash/test_file_trash_client.py index 125f04fc..fc66598b 100644 --- a/tests/app/services/file_manager/file_trash/test_file_trash_client.py +++ b/tests/app/services/file_manager/file_trash/test_file_trash_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -7,8 +7,7 @@ from app.configs.app_config import AppConfig from app.models.item import ItemStatus from app.services.file_manager.file_trash.file_trash_client import FileTrashClient -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import customized_error_msg +from app.services.output_manager.error_handler import ECustomizedError, customized_error_msg from tests.conftest import decoded_token diff --git a/tests/app/services/file_manager/file_upload/test_file_upload.py b/tests/app/services/file_manager/file_upload/test_file_upload.py index 5f5f800e..a3d454c3 100644 --- a/tests/app/services/file_manager/file_upload/test_file_upload.py +++ b/tests/app/services/file_manager/file_upload/test_file_upload.py @@ -1,21 +1,22 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. import os +from unittest.mock import MagicMock import click from app.configs.app_config import AppConfig from app.models.item import ItemType -from app.services.file_manager.file_upload.file_upload import assemble_path -from app.services.file_manager.file_upload.file_upload import compress_folder_to_zip -from app.services.file_manager.file_upload.file_upload import resume_upload -from app.services.file_manager.file_upload.file_upload import simple_upload -from app.services.file_manager.file_upload.models import FileObject -from app.services.file_manager.file_upload.models import ItemStatus -from app.services.output_manager.error_handler import ECustomizedError -from app.services.output_manager.error_handler import customized_error_msg +from app.services.file_manager.file_upload.file_upload import ( + assemble_path, + compress_folder_to_zip, + resume_upload, + simple_upload, +) +from app.services.file_manager.file_upload.models import FileObject, ItemStatus +from app.services.output_manager.error_handler import ECustomizedError, customized_error_msg from tests.conftest import decoded_token @@ -276,7 +277,7 @@ def test_folder_merge_succuss_with_no_duplication(mocker, mock_upload_client): non_dup_list = [FileObject('object/path', 'local_path', 'resumable_id', 'job_id', 'item_id')] mocker.patch( 'app.services.file_manager.file_upload.file_upload.UploadClient.check_upload_duplication', - return_value=(non_dup_list, []), + return_value=(non_dup_list, [], []), ) item_ids = simple_upload(upload_event) @@ -301,7 +302,7 @@ def test_folder_merge_succuss_with_duplication(mocker, mock_upload_client): dup_list = ['object/dup'] mocker.patch( 'app.services.file_manager.file_upload.file_upload.UploadClient.check_upload_duplication', - return_value=(non_dup_list, dup_list), + return_value=(non_dup_list, dup_list, []), ) item_ids = simple_upload(upload_event) @@ -326,7 +327,7 @@ def test_folder_merge_skip_with_all_duplication(mocker, mock_upload_client, capf dup_list = ['object/dup'] mocker.patch( 'app.services.file_manager.file_upload.file_upload.UploadClient.check_upload_duplication', - return_value=([], dup_list), + return_value=([], dup_list, []), ) try: @@ -368,7 +369,7 @@ def test_upload_folder_as_zip(mocker, mock_upload_client): non_dup_list = [FileObject('object/path', 'local_path', 'resumable_id', 'job_id', 'item_id')] mocker.patch( 'app.services.file_manager.file_upload.file_upload.UploadClient.check_upload_duplication', - return_value=(non_dup_list, []), + return_value=(non_dup_list, [], []), ) item_ids = simple_upload(upload_event) assert len(item_ids) == 1 @@ -391,6 +392,7 @@ def test_resume_upload(mocker): 'registered_items': {test_obj.item_id: test_obj.to_dict()}, 'unregistered_items': {}, 'total_size': 1, + 'resumable_manifest_file': 'resumable_manifest_file', } get_return = test_obj.to_dict() @@ -407,11 +409,15 @@ def test_resume_upload(mocker): 'os.path.getsize', return_value=1, ) + output_manifest_mock = mocker.patch( + 'app.services.file_manager.file_upload.file_upload.UploadClient.output_manifest', return_value={} + ) resume_upload(manifest_json, 1) get_mock.assert_called_once() resume_upload_mock.assert_called_once() + output_manifest_mock.assert_called_once() def test_resume_upload_failed_when_REGISTERED_doesnt_exist(mocker, capfd): @@ -427,6 +433,7 @@ def test_resume_upload_failed_when_REGISTERED_doesnt_exist(mocker, capfd): 'tags': 'tags', 'registered_items': {test_obj.item_id: test_obj.to_dict()}, 'unregistered_items': {}, + 'resumable_manifest_file': 'resumable_manifest_file', } get_return = test_obj.to_dict() @@ -437,6 +444,9 @@ def test_resume_upload_failed_when_REGISTERED_doesnt_exist(mocker, capfd): resume_upload_mock = mocker.patch( 'app.services.file_manager.file_upload.file_upload.UploadClient.resume_upload', return_value=[] ) + resumable_manifest_mock = mocker.patch( + 'app.services.file_manager.file_upload.file_upload.UploadClient.output_manifest', return_value={} + ) try: resume_upload(manifest_json, 1) @@ -447,6 +457,7 @@ def test_resume_upload_failed_when_REGISTERED_doesnt_exist(mocker, capfd): get_mock.assert_called_once() assert resume_upload_mock.call_count == 0 + assert resumable_manifest_mock.call_count == 0 def test_resume_upload_integrity_check_failed(mocker, capfd): @@ -484,6 +495,9 @@ def test_resume_upload_integrity_check_failed(mocker, capfd): 'os.path.getsize', return_value=2, ) + resumable_manifest_mock = mocker.patch( + 'app.services.file_manager.file_upload.file_upload.UploadClient.output_manifest', return_value={} + ) resume_upload(manifest_json, 1) out, _ = capfd.readouterr() @@ -492,3 +506,194 @@ def test_resume_upload_integrity_check_failed(mocker, capfd): get_mock.assert_called_once() resume_upload_mock.assert_called_once() + resumable_manifest_mock.assert_called_once() + + +def test_resume_upload_manifest_updates_correctly_after_each_batch(mocker): + """ + Test that the manifest file is updated correctly after each batch, + ensuring the remaining unregistered_items slice is accurate. + """ + # Mock dependencies + mocker.patch( + 'app.services.user_authentication.token_manager.SrvTokenManager.decode_access_token', + return_value=decoded_token(), + ) + mocker.patch('app.services.file_manager.file_upload.models.FileObject.generate_meta', return_value=(1, 1)) + + # Create test file objects + test_files = [] + for i in range(7): # 7 files, with batch size of 3, should be 3 batches: [3, 3, 1] + test_obj = FileObject(f'object/path_{i}', f'local_path_{i}', f'resumable_id_{i}', f'job_id_{i}', f'item_id_{i}') + test_obj.total_size = 1 + test_files.append(test_obj) + + manifest_json = { + 'project_code': 'project_code', + 'operator': 'operator', + 'zone': AppConfig.Env.green_zone, + 'parent_folder_id': 'parent_folder_id', + 'current_folder_node': 'current_folder_node', + 'tags': 'tags', + 'registered_items': {}, + 'unregistered_items': {f'object/path_{i}': test_files[i].to_dict() for i in range(7)}, + 'total_size': 7, + 'resumable_manifest_file': 'test_manifest.json', + } + + # Mock the upload client and its methods + mock_upload_client = MagicMock() + mock_upload_client.pre_upload.return_value = [] # Return empty list for simplicity + + # Track calls to output_manifest to verify correct remaining items + manifest_calls = [] + + def track_manifest_calls(finished_items, remaining_items, manifest_file): + manifest_calls.append( + { + 'finished_count': len(finished_items), + 'remaining_count': len(remaining_items), + 'remaining_items': [ + item.object_path if hasattr(item, 'object_path') else str(item) for item in remaining_items + ], + } + ) + + mock_upload_client.output_manifest.side_effect = track_manifest_calls + + # Mock the UploadClient constructor + mocker.patch('app.services.file_manager.file_upload.file_upload.UploadClient', return_value=mock_upload_client) + + # Mock other dependencies + mocker.patch('app.services.file_manager.file_upload.file_upload.resume_get_unfinished_items', return_value=[]) + mocker.patch( + 'app.services.file_manager.file_upload.file_upload.item_duplication_check', return_value=(test_files, []) + ) # Return all files as unregistered + mocker.patch( + 'app.services.file_manager.file_upload.file_upload.batch_generator', + side_effect=lambda items, batch_size: [items[i : i + batch_size] for i in range(0, len(items), batch_size)], + ) + + # Set batch size to 3 for testing + mocker.patch.object(AppConfig.Env, 'upload_batch_size', 3) + + # Mock threading components + mocker.patch('app.services.file_manager.file_upload.file_upload.ThreadPool') + mocker.patch('app.services.output_manager.message_handler.SrvOutPutHandler') + + # Execute the function + resume_upload(manifest_json, 1) + + # Verify output_manifest was called correctly for each batch + # Should be called 4 times: 1 initial + 3 batch calls + expected_calls = 4 + assert len(manifest_calls) == expected_calls, f"Expected {expected_calls} manifest calls, got {len(manifest_calls)}" + + # Verify the remaining items count decreases correctly after each batch + batch_calls = manifest_calls[1:] # Skip the initial call, focus on batch processing + + # First batch: processed 3, remaining 4 + assert ( + batch_calls[0]['remaining_count'] == 4 + ), f"After batch 1, expected 4 remaining, got {batch_calls[0]['remaining_count']}" + + # Second batch: processed 6, remaining 1 + assert ( + batch_calls[1]['remaining_count'] == 1 + ), f"After batch 2, expected 1 remaining, got {batch_calls[1]['remaining_count']}" + + # Third batch: processed 7, remaining 0 + assert ( + batch_calls[2]['remaining_count'] == 0 + ), f"After batch 3, expected 0 remaining, got {batch_calls[2]['remaining_count']}" + + # Verify the actual remaining items are correct (not just the count) + # After first batch, should have items 3,4,5,6 remaining + expected_remaining_after_batch1 = ['object/path_3', 'object/path_4', 'object/path_5', 'object/path_6'] + assert batch_calls[0]['remaining_items'] == expected_remaining_after_batch1 + + # After second batch, should have item 6 remaining + expected_remaining_after_batch2 = ['object/path_6'] + assert batch_calls[1]['remaining_items'] == expected_remaining_after_batch2 + + # After third batch, should have no items remaining + assert batch_calls[2]['remaining_items'] == [] + + +def test_resume_upload_manifest_handles_uneven_batches(mocker): + """ + Test that manifest updates work correctly with uneven batch sizes + (e.g., when the last batch has fewer items than batch_size) + """ + # Mock dependencies + mocker.patch( + 'app.services.user_authentication.token_manager.SrvTokenManager.decode_access_token', + return_value=decoded_token(), + ) + mocker.patch('app.services.file_manager.file_upload.models.FileObject.generate_meta', return_value=(1, 1)) + + # Create 5 test files with batch size of 3 -> batches: [3, 2] + test_files = [] + for i in range(5): + test_obj = FileObject(f'object/path_{i}', f'local_path_{i}', f'resumable_id_{i}', f'job_id_{i}', f'item_id_{i}') + test_obj.total_size = 1 + test_files.append(test_obj) + + manifest_json = { + 'project_code': 'project_code', + 'operator': 'operator', + 'zone': AppConfig.Env.green_zone, + 'parent_folder_id': 'parent_folder_id', + 'current_folder_node': 'current_folder_node', + 'tags': 'tags', + 'registered_items': {}, + 'unregistered_items': {f'object/path_{i}': test_files[i].to_dict() for i in range(5)}, + 'total_size': 5, + 'resumable_manifest_file': 'test_manifest.json', + } + + # Mock the upload client + mock_upload_client = MagicMock() + mock_upload_client.pre_upload.return_value = [] + + # Track manifest calls + manifest_calls = [] + + def track_manifest_calls(finished_items, remaining_items, manifest_file): + manifest_calls.append( + { + 'remaining_count': len(remaining_items), + } + ) + + mock_upload_client.output_manifest.side_effect = track_manifest_calls + + # Mock dependencies + mocker.patch('app.services.file_manager.file_upload.file_upload.UploadClient', return_value=mock_upload_client) + mocker.patch('app.services.file_manager.file_upload.file_upload.resume_get_unfinished_items', return_value=[]) + mocker.patch( + 'app.services.file_manager.file_upload.file_upload.item_duplication_check', return_value=(test_files, []) + ) + mocker.patch( + 'app.services.file_manager.file_upload.file_upload.batch_generator', + side_effect=lambda items, batch_size: [items[i : i + batch_size] for i in range(0, len(items), batch_size)], + ) + + # Set batch size to 3 + mocker.patch.object(AppConfig.Env, 'upload_batch_size', 3) + + # Mock threading components + mocker.patch('app.services.file_manager.file_upload.file_upload.ThreadPool') + mocker.patch('app.services.output_manager.message_handler.SrvOutPutHandler') + + # Execute the function + resume_upload(manifest_json, 1) + + # Should have 3 calls: 1 initial + 2 batch calls + batch_calls = manifest_calls[1:] # Skip initial call + + # After first batch (3 items processed): remaining = 2 + assert batch_calls[0]['remaining_count'] == 2 + + # After second batch (5 items processed): remaining = 0 + assert batch_calls[1]['remaining_count'] == 0 diff --git a/tests/app/services/file_manager/file_upload/test_model.py b/tests/app/services/file_manager/file_upload/test_model.py index 48ad67e2..6d333915 100644 --- a/tests/app/services/file_manager/file_upload/test_model.py +++ b/tests/app/services/file_manager/file_upload/test_model.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/services/file_manager/file_upload/test_upload_client.py b/tests/app/services/file_manager/file_upload/test_upload_client.py index 37c18c64..2a62ac3b 100644 --- a/tests/app/services/file_manager/file_upload/test_upload_client.py +++ b/tests/app/services/file_manager/file_upload/test_upload_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -313,17 +313,32 @@ def test_check_upload_duplication_success(httpx_mock, mocker, case_insensitive): mocker.patch('app.services.file_manager.file_upload.models.FileObject.generate_meta', return_value=(1, 1)) dup_obj = FileObject('object/duplicate', 'local_path', 'resumable_id', 'job_id', 'item_id') not_dup_object = FileObject('object/not_duplicate', 'local_path', 'resumable_id', 'job_id', 'item_id') + registered_item = FileObject('object/registered', 'local_path', 'resumable_id', 'job_id', 'item_id') - url = AppConfig.Connections.url_base + '/portal/v1/files/exists' + url = AppConfig.Connections.url_bff + '/v2/items/batch/exists' httpx_mock.add_response( method='POST', url=url, - json={'result': [dup_obj.object_path.upper() if case_insensitive else dup_obj.object_path]}, + json={ + 'result': [ + {'parent_path': 'object', 'name': 'duplicate', 'status': ItemStatus.ACTIVE}, + {'parent_path': 'object', 'name': 'registered', 'status': ItemStatus.REGISTERED}, + ] + }, ) - not_dup_list, dup_list = upload_client.check_upload_duplication([dup_obj, not_dup_object]) - assert not_dup_list == [not_dup_object] - assert dup_list == [dup_obj.object_path.upper() if case_insensitive else dup_obj.object_path] + not_dup_list, dup_list, registered_list = upload_client.check_upload_duplication([dup_obj, not_dup_object]) + + assert len(not_dup_list) == 1 + assert not_dup_list[0] == not_dup_object + + assert len(dup_list) == 1 + assert dup_list[0] == dup_obj.object_path + + assert len(registered_list) == 1 + assert registered_item.object_path == registered_list[0].get('parent_path', '') + '/' + registered_list[0].get( + 'name', '' + ) def test_check_upload_duplication_fail_with_403(httpx_mock, mocker, capfd): @@ -333,7 +348,7 @@ def test_check_upload_duplication_fail_with_403(httpx_mock, mocker, capfd): ) upload_client = UploadClient('project_code', 'parent_folder_id') - url = AppConfig.Connections.url_base + '/portal/v1/files/exists' + url = AppConfig.Connections.url_bff + '/v2/items/batch/exists' httpx_mock.add_response( method='POST', url=url, @@ -360,7 +375,7 @@ def test_check_upload_duplication_fail_with_500(httpx_mock, mocker, capfd): ) upload_client = UploadClient('project_code', 'parent_folder_id') - url = AppConfig.Connections.url_base + '/portal/v1/files/exists' + url = AppConfig.Connections.url_bff + '/v2/items/batch/exists' httpx_mock.add_response( method='POST', url=url, @@ -403,7 +418,7 @@ def test_output_manifest_success_for_resumable_upload(mocker, tmp_path): assert file_item.get('item_id') == 'item_id_1' assert len(res.get('unregistered_items')) == 1 - file_item = res.get('unregistered_items').get('local_path_2') # unregistered item dont have id + file_item = res.get('unregistered_items').get('object/test_obj_unregistered') # unregistered item dont have id assert file_item.get('resumable_id') == 'resumable_id_2' assert file_item.get('local_path') == 'local_path_2' assert file_item.get('object_path') == 'object/test_obj_unregistered' diff --git a/tests/app/services/file_manager/test_manifest.py b/tests/app/services/file_manager/test_manifest.py index 86f9e42a..2509f57d 100644 --- a/tests/app/services/file_manager/test_manifest.py +++ b/tests/app/services/file_manager/test_manifest.py @@ -1,4 +1,4 @@ -# Copyright (C) 2025 Indoc Systems +# Copyright (C) 2025-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/services/project_manager/test_project.py b/tests/app/services/project_manager/test_project.py index 08c11fd2..e6a551ff 100644 --- a/tests/app/services/project_manager/test_project.py +++ b/tests/app/services/project_manager/test_project.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/services/user_authentication/test_token_manager.py b/tests/app/services/user_authentication/test_token_manager.py index 3ca14d30..4f4ec355 100644 --- a/tests/app/services/user_authentication/test_token_manager.py +++ b/tests/app/services/user_authentication/test_token_manager.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023-2025 Indoc Systems +# Copyright (C) 2023-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/app/services/user_authentication/test_user_login_logout.py b/tests/app/services/user_authentication/test_user_login_logout.py index c9d3e799..7a532198 100644 --- a/tests/app/services/user_authentication/test_user_login_logout.py +++ b/tests/app/services/user_authentication/test_user_login_logout.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -6,9 +6,11 @@ from app.configs.app_config import AppConfig from app.configs.user_config import UserConfig -from app.services.user_authentication.user_login_logout import check_is_login -from app.services.user_authentication.user_login_logout import user_device_id_login -from app.services.user_authentication.user_login_logout import validate_user_device_login +from app.services.user_authentication.user_login_logout import ( + check_is_login, + user_device_id_login, + validate_user_device_login, +) def test_check_is_not_login(mocker): diff --git a/tests/app/utils/test_aggregated.py b/tests/app/utils/test_aggregated.py index 5914ce0f..7156cb1c 100644 --- a/tests/app/utils/test_aggregated.py +++ b/tests/app/utils/test_aggregated.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -6,14 +6,16 @@ from app.configs.app_config import AppConfig from app.models.item import ItemType -from app.utils.aggregated import check_item_duplication -from app.utils.aggregated import get_latest_cli_version -from app.utils.aggregated import get_version_compatibility -from app.utils.aggregated import identify_target_folder -from app.utils.aggregated import normalize_input_paths -from app.utils.aggregated import normalize_join -from app.utils.aggregated import search_item -from app.utils.aggregated import validate_folder_name +from app.utils.aggregated import ( + check_item_duplication, + get_latest_cli_version, + get_version_compatibility, + identify_target_folder, + normalize_input_paths, + normalize_join, + search_item, + validate_folder_name, +) from tests.conftest import decoded_token test_project_code = 'testproject' diff --git a/tests/conftest.py b/tests/conftest.py index d39b4f62..846c42b6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2025 Indoc Systems +# Copyright (C) 2022-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. @@ -7,8 +7,7 @@ import pytest from app.configs.app_config import AppConfig -from app.configs.config import Settings -from app.configs.config import get_settings +from app.configs.config import Settings, get_settings from app.configs.user_config import UserConfig from app.models.singleton import Singleton diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py index 0886395d..c7720cf6 100644 --- a/tests/fixtures/__init__.py +++ b/tests/fixtures/__init__.py @@ -1,3 +1,3 @@ -# Copyright (C) 2023-2025 Indoc Systems +# Copyright (C) 2023-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code. diff --git a/tests/fixtures/fake.py b/tests/fixtures/fake.py index a252aaba..aad24a64 100644 --- a/tests/fixtures/fake.py +++ b/tests/fixtures/fake.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023-2025 Indoc Systems +# Copyright (C) 2023-2026 Indoc Systems # # Contact Indoc Systems for any questions regarding the use of this source code.