diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ecb2c8f3..29b6f72d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,21 +86,17 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "ubuntu-24.04-arm", "macos-latest", "windows-latest"] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "pypy3.9", "pypy3.10"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy3.9", "pypy3.10"] exclude: - os: "macos-latest" python-version: "pypy3.10" - os: "windows-latest" python-version: "pypy3.10" # Workaround for https://github.com/actions/setup-python/issues/696 - - os: "macos-latest" - python-version: 3.8 - os: "macos-latest" python-version: 3.9 include: # Workaround for https://github.com/actions/setup-python/issues/696 - - os: "macos-13" - python-version: 3.8 - os: "macos-13" python-version: 3.9 steps: @@ -129,7 +125,7 @@ jobs: run: nox -vs integration -p ${{ matrix.python-version }} -- -m "not require_secrets" - name: Run integration tests (with secrets) # Limit CI workload by running integration tests with secrets only on edge Python versions. - if: ${{ env.B2_TEST_APPLICATION_KEY != '' && env.B2_TEST_APPLICATION_KEY_ID != '' && contains(fromJSON('["3.8", "pypy3.10", "3.13"]'), matrix.python-version) }} + if: ${{ env.B2_TEST_APPLICATION_KEY != '' && env.B2_TEST_APPLICATION_KEY_ID != '' && contains(fromJSON('["3.9", "pypy3.10", "3.13"]'), matrix.python-version) }} run: nox -vs integration -p ${{ matrix.python-version }} -- -m "require_secrets" --cleanup test-docker: timeout-minutes: 90 diff --git a/.gitignore b/.gitignore index c661e46e1..c19b3d8a4 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ doc/source/main_help.rst doc/source/subcommands Dockerfile b2/licenses_output.txt -*.spec \ No newline at end of file +*.spec +.env \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aae12d765..b7772dc82 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ With `nox`, you can run different sessions (default are `lint` and `test`): * `format` -> Format the code. * `lint` -> Run linters. -* `test` (`test-3.8`, `test-3.9`, `test-3.10`, `test-3.11`) -> Run test suite. +* `test` (`test-3.9`, `test-3.10`, `test-3.11`) -> Run test suite. * `cover` -> Perform coverage analysis. * `build` -> Build the distribution. * `generate_dockerfile` -> generate dockerfile diff --git a/b2/_internal/_cli/argcompleters.py b/b2/_internal/_cli/argcompleters.py index d997f25d9..b2db5d23d 100644 --- a/b2/_internal/_cli/argcompleters.py +++ b/b2/_internal/_cli/argcompleters.py @@ -61,12 +61,11 @@ def b2uri_file_completer(prefix: str, parsed_args, **kwargs): from b2sdk.v3 import LIST_FILE_NAMES_MAX_LIMIT, unprintable_to_hex from b2._internal._cli.b2api import _get_b2api_for_profile - from b2._internal._utils.python_compat import removeprefix from b2._internal._utils.uri import parse_b2_uri api = _get_b2api_for_profile(getattr(parsed_args, 'profile', None)) if prefix.startswith('b2://'): - prefix_without_scheme = removeprefix(prefix, 'b2://') + prefix_without_scheme = prefix.removeprefix('b2://') if '/' not in prefix_without_scheme: return [ f'b2://{unprintable_to_hex(bucket.name)}/' diff --git a/b2/_internal/_cli/b2args.py b/b2/_internal/_cli/b2args.py index f06e824a8..8a3cc9c6c 100644 --- a/b2/_internal/_cli/b2args.py +++ b/b2/_internal/_cli/b2args.py @@ -14,7 +14,7 @@ import argparse import functools from os import environ -from typing import Optional, Tuple, Union +from typing import Optional, Union from b2._internal._cli.arg_parser_types import wrap_with_argument_type_error from b2._internal._cli.argcompleters import b2uri_file_completer, bucket_name_completer @@ -242,5 +242,5 @@ def add_b2id_or_file_like_b2_uri_or_bucket_name_argument( return arg -def get_keyid_and_key_from_env_vars() -> Tuple[Optional[str], Optional[str]]: +def get_keyid_and_key_from_env_vars() -> tuple[Optional[str], Optional[str]]: return environ.get(B2_APPLICATION_KEY_ID_ENV_VAR), environ.get(B2_APPLICATION_KEY_ENV_VAR) diff --git a/b2/_internal/_utils/python_compat.py b/b2/_internal/_utils/python_compat.py deleted file mode 100644 index a561edd14..000000000 --- a/b2/_internal/_utils/python_compat.py +++ /dev/null @@ -1,22 +0,0 @@ -###################################################################### -# -# File: b2/_internal/_utils/python_compat.py -# -# Copyright 2023 Backblaze Inc. All Rights Reserved. -# -# License https://www.backblaze.com/using_b2_code.html -# -###################################################################### -""" -Utilities for compatibility with older Python versions. -""" - -import sys - -if sys.version_info < (3, 9): - - def removeprefix(s: str, prefix: str) -> str: - return s[len(prefix) :] if s.startswith(prefix) else s - -else: - removeprefix = str.removeprefix diff --git a/b2/_internal/_utils/uri.py b/b2/_internal/_utils/uri.py index 22009f727..3be7aa725 100644 --- a/b2/_internal/_utils/uri.py +++ b/b2/_internal/_utils/uri.py @@ -11,9 +11,9 @@ import dataclasses import re +from collections.abc import Sequence from functools import singledispatchmethod from pathlib import Path -from typing import Sequence from b2sdk.v3 import ( B2Api, diff --git a/b2/_internal/console_tool.py b/b2/_internal/console_tool.py index e255dc1fe..48481234f 100644 --- a/b2/_internal/console_tool.py +++ b/b2/_internal/console_tool.py @@ -17,7 +17,6 @@ import warnings from b2._internal._cli.autocomplete_cache import AUTOCOMPLETE # noqa -from b2._internal._utils.python_compat import removeprefix AUTOCOMPLETE.autocomplete_from_cache() @@ -49,7 +48,7 @@ from concurrent.futures import Executor, Future, ThreadPoolExecutor from contextlib import suppress from enum import Enum -from typing import Any, BinaryIO, List +from typing import Any, BinaryIO import b2sdk import requests @@ -782,7 +781,7 @@ def _setup_parser(cls, parser): super()._setup_parser(parser) def get_b2_uri_from_arg(self, args: argparse.Namespace) -> B2URI: - return B2URI(removeprefix(args.bucketName or '', 'b2://'), args.folderName or '') + return B2URI((args.bucketName or '').removeprefix('b2://'), args.folderName or '') class B2IDOrB2URIMixin: @@ -884,7 +883,7 @@ def _setup_parser(cls, parser): add_normalized_argument( lifecycle_group, '--lifecycle-rules', - type=functools.partial(validated_loads, expected_type=List[LifecycleRule]), + type=functools.partial(validated_loads, expected_type=list[LifecycleRule]), help='(deprecated; use --lifecycle-rule instead) List of lifecycle rules in JSON format.', ) diff --git a/b2/_internal/version_listing.py b/b2/_internal/version_listing.py index 779b23d48..67f155343 100644 --- a/b2/_internal/version_listing.py +++ b/b2/_internal/version_listing.py @@ -10,12 +10,11 @@ import pathlib import re -from typing import List RE_VERSION = re.compile(r'[_]*b2v(\d+)') -def get_versions() -> List[str]: +def get_versions() -> list[str]: return [path.name for path in sorted(pathlib.Path(__file__).parent.glob('*b2v*'))] diff --git a/changelog.d/+b2sdk_testing.infrastructure.md b/changelog.d/+b2sdk_testing.infrastructure.md new file mode 100644 index 000000000..65e5b6164 --- /dev/null +++ b/changelog.d/+b2sdk_testing.infrastructure.md @@ -0,0 +1 @@ +Use b2sdk pytest plugin and utilities in tests. \ No newline at end of file diff --git a/changelog.d/+python38.removed.md b/changelog.d/+python38.removed.md new file mode 100644 index 000000000..0aaffd165 --- /dev/null +++ b/changelog.d/+python38.removed.md @@ -0,0 +1 @@ +Dropped support for python 3.8. \ No newline at end of file diff --git a/noxfile.py b/noxfile.py index 995f6fb58..9e90adcee 100644 --- a/noxfile.py +++ b/noxfile.py @@ -36,7 +36,6 @@ [ 'pypy3.9', 'pypy3.10', - '3.8', '3.9', '3.10', '3.11', diff --git a/pdm.lock b/pdm.lock index b81389323..2e7d9c244 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,26 +5,7 @@ groups = ["default", "bundle", "doc", "format", "full", "license", "lint", "release", "test"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:e1d038a0b19e93fa03527a3d9515e91fc04449568cbbb6655c06a864fdf1dac8" - -[[metadata.targets]] -requires_python = ">=3.9" -platform = "manylinux_2_17_x86_64" - -[[metadata.targets]] -requires_python = "==3.8.*" -platform = "manylinux_2_17_x86_64" - -[[metadata.targets]] -requires_python = ">=3.9" -platform = "windows_amd64" - -[[metadata.targets]] -requires_python = "==3.8.*" -platform = "windows_amd64" - -[[metadata.targets]] -requires_python = "==3.8.*" +content_hash = "sha256:46405cf2d9f58fc06083ecfc0a3bbcf286cd0bf26e8bc39565259ae02f86d75c" [[metadata.targets]] requires_python = ">=3.9" @@ -35,7 +16,6 @@ version = "0.7.16" requires_python = ">=3.9" summary = "A light, configurable Sphinx theme" groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, @@ -46,7 +26,7 @@ name = "altgraph" version = "0.17.4" summary = "Python graph (network) package" groups = ["bundle"] -marker = "python_version < \"3.13\" and python_version >= \"3.9\" or python_version == \"3.8\"" +marker = "python_version < \"3.13\"" files = [ {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"}, {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, @@ -68,11 +48,10 @@ files = [ [[package]] name = "anyio" -version = "4.9.0" +version = "4.10.0" requires_python = ">=3.9" -summary = "High level compatibility layer for multiple asynchronous event loop implementations" +summary = "High-level concurrency and networking framework on top of asyncio or Trio" groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "exceptiongroup>=1.0.2; python_version < \"3.11\"", "idna>=2.8", @@ -80,8 +59,8 @@ dependencies = [ "typing-extensions>=4.5; python_version < \"3.13\"", ] files = [ - {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, - {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, + {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, + {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, ] [[package]] @@ -112,8 +91,8 @@ files = [ [[package]] name = "b2sdk" -version = "2.9.4" -requires_python = ">=3.8" +version = "2.10.0" +requires_python = ">=3.9" summary = "Backblaze B2 SDK" groups = ["default"] dependencies = [ @@ -124,8 +103,8 @@ dependencies = [ "typing-extensions>=4.7.1; python_version < \"3.12\"", ] files = [ - {file = "b2sdk-2.9.4-py3-none-any.whl", hash = "sha256:60d9a144da8cd7e3e432e7d7a95e8b92d3be82e1921270b1108ab947d5bfeaad"}, - {file = "b2sdk-2.9.4.tar.gz", hash = "sha256:7e47ec9538c8cb483a91ee9e6e38dd0d93319b815aa0c4e8cd4cf8def8f2c8e6"}, + {file = "b2sdk-2.10.0-py3-none-any.whl", hash = "sha256:5bb587830a840b60ed148765b29870d251c6d02408712c5c3447725a1414d52d"}, + {file = "b2sdk-2.10.0.tar.gz", hash = "sha256:be41a07a49ed332129cf3d73262308774187b230862731e473485400a89f8dc3"}, ] [[package]] @@ -134,7 +113,6 @@ version = "2.17.0" requires_python = ">=3.8" summary = "Internationalization utilities" groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "pytz>=2015.7; python_version < \"3.9\"", ] @@ -145,102 +123,90 @@ files = [ [[package]] name = "certifi" -version = "2025.7.14" +version = "2025.8.3" requires_python = ">=3.7" summary = "Python package for providing Mozilla's CA Bundle." groups = ["default", "doc"] files = [ - {file = "certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2"}, - {file = "certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995"}, + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, ] [[package]] name = "charset-normalizer" -version = "3.4.2" +version = "3.4.3" requires_python = ">=3.7" summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." groups = ["default", "doc"] 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"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, - {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, - {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, ] [[package]] @@ -264,7 +230,6 @@ version = "0.4.6" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" summary = "Cross-platform colored terminal text." groups = ["default", "doc", "lint", "release", "test"] -marker = "sys_platform == \"win32\" or platform_system == \"Windows\" or python_version >= \"3.9\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -306,16 +271,6 @@ files = [ {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, @@ -371,16 +326,6 @@ files = [ {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, @@ -401,31 +346,18 @@ version = "0.18.1" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" summary = "Docutils -- Python Documentation Utilities" groups = ["default", "doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, ] -[[package]] -name = "docutils" -version = "0.20.1" -requires_python = ">=3.7" -summary = "Docutils -- Python Documentation Utilities" -groups = ["default", "doc"] -marker = "python_version == \"3.8\"" -files = [ - {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, - {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, -] - [[package]] name = "exceptiongroup" version = "1.3.0" requires_python = ">=3.7" summary = "Backport of PEP 654 (exception groups)" groups = ["doc", "lint", "test"] -marker = "python_version < \"3.11\" and python_version >= \"3.9\" or python_version == \"3.8\"" +marker = "python_version < \"3.11\"" dependencies = [ "typing-extensions>=4.6.0; python_version < \"3.13\"", ] @@ -447,66 +379,66 @@ files = [ [[package]] name = "greenlet" -version = "3.2.3" +version = "3.2.4" requires_python = ">=3.9" summary = "Lightweight in-process concurrent programming" groups = ["doc"] -marker = "(platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\") and python_version < \"3.14\" and python_version >= \"3.9\"" -files = [ - {file = "greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be"}, - {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac"}, - {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392"}, - {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c"}, - {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db"}, - {file = "greenlet-3.2.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b"}, - {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712"}, - {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00"}, - {file = "greenlet-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302"}, - {file = "greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822"}, - {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83"}, - {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf"}, - {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b"}, - {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147"}, - {file = "greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5"}, - {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc"}, - {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba"}, - {file = "greenlet-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34"}, - {file = "greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d"}, - {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b"}, - {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d"}, - {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264"}, - {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688"}, - {file = "greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb"}, - {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c"}, - {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163"}, - {file = "greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849"}, - {file = "greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad"}, - {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef"}, - {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3"}, - {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95"}, - {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb"}, - {file = "greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b"}, - {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0"}, - {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36"}, - {file = "greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3"}, - {file = "greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86"}, - {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97"}, - {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728"}, - {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a"}, - {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892"}, - {file = "greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141"}, - {file = "greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a"}, - {file = "greenlet-3.2.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:42efc522c0bd75ffa11a71e09cd8a399d83fafe36db250a87cf1dacfaa15dc64"}, - {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d760f9bdfe79bff803bad32b4d8ffb2c1d2ce906313fc10a83976ffb73d64ca7"}, - {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8324319cbd7b35b97990090808fdc99c27fe5338f87db50514959f8059999805"}, - {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8c37ef5b3787567d322331d5250e44e42b58c8c713859b8a04c6065f27efbf72"}, - {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ce539fb52fb774d0802175d37fcff5c723e2c7d249c65916257f0a940cee8904"}, - {file = "greenlet-3.2.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:003c930e0e074db83559edc8705f3a2d066d4aa8c2f198aff1e454946efd0f26"}, - {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7e70ea4384b81ef9e84192e8a77fb87573138aa5d4feee541d8014e452b434da"}, - {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:22eb5ba839c4b2156f18f76768233fe44b23a31decd9cc0d4cc8141c211fd1b4"}, - {file = "greenlet-3.2.3-cp39-cp39-win32.whl", hash = "sha256:4532f0d25df67f896d137431b13f4cdce89f7e3d4a96387a41290910df4d3a57"}, - {file = "greenlet-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:aaa7aae1e7f75eaa3ae400ad98f8644bb81e1dc6ba47ce8a93d3f17274e08322"}, - {file = "greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365"}, +marker = "(platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\") and python_version < \"3.14\"" +files = [ + {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, + {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, + {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, + {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, + {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, + {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, + {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, + {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, + {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, + {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, + {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, + {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, + {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, + {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, ] [[package]] @@ -515,7 +447,6 @@ version = "0.16.0" requires_python = ">=3.8" summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -538,7 +469,6 @@ version = "1.4.1" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" summary = "Getting image size from png/jpeg/jpeg2000/gif file" groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, @@ -550,7 +480,7 @@ version = "8.7.0" requires_python = ">=3.9" summary = "Read metadata from Python packages" groups = ["bundle", "doc"] -marker = "python_version < \"3.10\" and python_version >= \"3.9\"" +marker = "python_version < \"3.10\"" dependencies = [ "typing-extensions>=3.6.4; python_version < \"3.8\"", "zipp>=3.20", @@ -560,29 +490,13 @@ files = [ {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, ] -[[package]] -name = "importlib-metadata" -version = "8.5.0" -requires_python = ">=3.8" -summary = "Read metadata from Python packages" -groups = ["bundle"] -marker = "python_version == \"3.8\"" -dependencies = [ - "typing-extensions>=3.6.4; python_version < \"3.8\"", - "zipp>=3.20", -] -files = [ - {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, - {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, -] - [[package]] name = "importlib-resources" version = "6.5.2" requires_python = ">=3.9" summary = "Read resources from Python packages" groups = ["release"] -marker = "python_version < \"3.10\" and python_version >= \"3.9\"" +marker = "python_version < \"3.10\"" dependencies = [ "zipp>=3.1.0; python_version < \"3.10\"", ] @@ -591,21 +505,6 @@ files = [ {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, ] -[[package]] -name = "importlib-resources" -version = "6.4.5" -requires_python = ">=3.8" -summary = "Read resources from Python packages" -groups = ["release"] -marker = "python_version == \"3.8\"" -dependencies = [ - "zipp>=3.1.0; python_version < \"3.10\"", -] -files = [ - {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, - {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, -] - [[package]] name = "incremental" version = "24.7.2" @@ -676,7 +575,7 @@ name = "macholib" version = "1.16.3" summary = "Mach-O header analysis and editing" groups = ["bundle"] -marker = "sys_platform == \"darwin\" and python_version < \"3.13\" and (python_version == \"3.8\" or python_version >= \"3.9\")" +marker = "sys_platform == \"darwin\" and python_version < \"3.13\"" dependencies = [ "altgraph>=0.17", ] @@ -691,7 +590,6 @@ version = "3.0.2" requires_python = ">=3.9" summary = "Safely add untrusted strings to HTML/XML markup." groups = ["doc", "release"] -marker = "python_version >= \"3.9\"" files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -756,27 +654,6 @@ files = [ {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] -[[package]] -name = "markupsafe" -version = "2.1.5" -requires_python = ">=3.7" -summary = "Safely add untrusted strings to HTML/XML markup." -groups = ["release"] -marker = "python_version == \"3.8\"" -files = [ - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - [[package]] name = "more-itertools" version = "8.13.0" @@ -815,7 +692,7 @@ version = "2024.8.26" requires_python = ">=3.6.0" summary = "Python PE parsing module" groups = ["bundle"] -marker = "sys_platform == \"win32\" and python_version < \"3.13\" and (python_version >= \"3.9\" or python_version == \"3.8\")" +marker = "sys_platform == \"win32\" and python_version < \"3.13\"" files = [ {file = "pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f"}, {file = "pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632"}, @@ -847,26 +724,13 @@ files = [ [[package]] name = "pip" -version = "25.1.1" +version = "25.2" requires_python = ">=3.9" summary = "The PyPA recommended tool for installing Python packages." groups = ["license"] -marker = "python_version >= \"3.9\"" files = [ - {file = "pip-25.1.1-py3-none-any.whl", hash = "sha256:2913a38a2abf4ea6b64ab507bd9e967f3b53dc1ede74b01b0931e1ce548751af"}, - {file = "pip-25.1.1.tar.gz", hash = "sha256:3de45d411d308d5054c2168185d8da7f9a2cd753dbac8acbfa88a8909ecd9077"}, -] - -[[package]] -name = "pip" -version = "25.0.1" -requires_python = ">=3.8" -summary = "The PyPA recommended tool for installing Python packages." -groups = ["license"] -marker = "python_version == \"3.8\"" -files = [ - {file = "pip-25.0.1-py3-none-any.whl", hash = "sha256:c46efd13b6aa8279f33f2864459c8ce587ea6a1a59ee20de055868d8f7688f7f"}, - {file = "pip-25.0.1.tar.gz", hash = "sha256:88f96547ea48b940a3a385494e181e29fb8637898f88d88737c5049780f196ea"}, + {file = "pip-25.2-py3-none-any.whl", hash = "sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717"}, + {file = "pip-25.2.tar.gz", hash = "sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2"}, ] [[package]] @@ -875,7 +739,6 @@ version = "5.0.0" requires_python = ">=3.9" summary = "Dump the software license list of Python packages installed with pip." groups = ["license"] -marker = "python_version >= \"3.9\"" dependencies = [ "prettytable>=2.3.0", "tomli>=2", @@ -885,28 +748,12 @@ files = [ {file = "pip_licenses-5.0.0.tar.gz", hash = "sha256:0633a1f9aab58e5a6216931b0e1d5cdded8bcc2709ff563674eb0e2ff9e77e8e"}, ] -[[package]] -name = "pip-licenses" -version = "3.5.5" -requires_python = "~=3.7" -summary = "Dump the software license list of Python packages installed with pip." -groups = ["license"] -marker = "python_version == \"3.8\"" -dependencies = [ - "PTable", -] -files = [ - {file = "pip-licenses-3.5.5.tar.gz", hash = "sha256:748cfd7aca6e05032f9fa85691301295f4d943e87955be6914ca49abe3c075a4"}, - {file = "pip_licenses-3.5.5-py3-none-any.whl", hash = "sha256:6129c116bab2b202d90d6e3a96092df4ad84c0c4d57bb70192fc03f8bf06d181"}, -] - [[package]] name = "pipdeptree" version = "2.28.0" requires_python = ">=3.9" summary = "Command line utility to show dependency tree of packages." groups = ["license"] -marker = "python_version >= \"3.9\"" dependencies = [ "packaging>=24.1", "pip>=24.2", @@ -922,55 +769,28 @@ version = "4.3.8" requires_python = ">=3.9" summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." groups = ["default"] -marker = "python_version >= \"3.9\"" files = [ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, ] -[[package]] -name = "platformdirs" -version = "4.3.6" -requires_python = ">=3.8" -summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -groups = ["default"] -marker = "python_version == \"3.8\"" -files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, -] - [[package]] name = "pluggy" version = "1.6.0" requires_python = ">=3.9" summary = "plugin and hook calling mechanisms for python" groups = ["lint", "test"] -marker = "python_version >= \"3.9\"" files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] -[[package]] -name = "pluggy" -version = "1.5.0" -requires_python = ">=3.8" -summary = "plugin and hook calling mechanisms for python" -groups = ["lint", "test"] -marker = "python_version == \"3.8\"" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - [[package]] name = "prettytable" version = "3.16.0" requires_python = ">=3.9" summary = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" groups = ["license"] -marker = "python_version >= \"3.9\"" dependencies = [ "wcwidth", ] @@ -979,31 +799,6 @@ files = [ {file = "prettytable-3.16.0.tar.gz", hash = "sha256:3c64b31719d961bf69c9a7e03d0c1e477320906a98da63952bc6698d6164ff57"}, ] -[[package]] -name = "prettytable" -version = "3.11.0" -requires_python = ">=3.8" -summary = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" -groups = ["license"] -marker = "python_version == \"3.8\"" -dependencies = [ - "wcwidth", -] -files = [ - {file = "prettytable-3.11.0-py3-none-any.whl", hash = "sha256:aa17083feb6c71da11a68b2c213b04675c4af4ce9c541762632ca3f2cb3546dd"}, - {file = "prettytable-3.11.0.tar.gz", hash = "sha256:7e23ca1e68bbfd06ba8de98bf553bf3493264c96d5e8a615c0471025deeba722"}, -] - -[[package]] -name = "ptable" -version = "0.9.2" -summary = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" -groups = ["license"] -marker = "python_version == \"3.8\"" -files = [ - {file = "PTable-0.9.2.tar.gz", hash = "sha256:aa7fc151cb40f2dabcd2275ba6f7fd0ff8577a86be3365cd3fb297cbe09cc292"}, -] - [[package]] name = "ptyprocess" version = "0.7.0" @@ -1031,7 +826,6 @@ version = "2.11.7" requires_python = ">=3.9" summary = "Data validation using Python type hints" groups = ["full"] -marker = "python_version >= \"3.9\"" dependencies = [ "annotated-types>=0.6.0", "pydantic-core==2.33.2", @@ -1043,30 +837,12 @@ files = [ {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, ] -[[package]] -name = "pydantic" -version = "2.10.6" -requires_python = ">=3.8" -summary = "Data validation using Python type hints" -groups = ["full"] -marker = "python_version == \"3.8\"" -dependencies = [ - "annotated-types>=0.6.0", - "pydantic-core==2.27.2", - "typing-extensions>=4.12.2", -] -files = [ - {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, - {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, -] - [[package]] name = "pydantic-core" version = "2.33.2" requires_python = ">=3.9" summary = "Core functionality for Pydantic validation and serialization" groups = ["full"] -marker = "python_version >= \"3.9\"" dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] @@ -1172,33 +948,6 @@ files = [ {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, ] -[[package]] -name = "pydantic-core" -version = "2.27.2" -requires_python = ">=3.8" -summary = "Core functionality for Pydantic validation and serialization" -groups = ["full"] -marker = "python_version == \"3.8\"" -dependencies = [ - "typing-extensions!=4.7.0,>=4.6.0", -] -files = [ - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, - {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, -] - [[package]] name = "pyelftools" version = "0.32" @@ -1216,7 +965,6 @@ version = "2.19.2" requires_python = ">=3.8" summary = "Pygments is a syntax highlighting package written in Python." groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1228,7 +976,7 @@ version = "5.13.2" requires_python = "<3.13,>=3.7" summary = "PyInstaller bundles a Python application and all its dependencies into a single package." groups = ["bundle"] -marker = "python_version < \"3.13\" and python_version >= \"3.9\" or python_version == \"3.8\"" +marker = "python_version < \"3.13\"" dependencies = [ "altgraph", "importlib-metadata>=1.4; python_version < \"3.8\"", @@ -1369,7 +1117,7 @@ version = "0.2.3" requires_python = ">=3.6" summary = "A (partial) reimplementation of pywin32 using ctypes/cffi" groups = ["bundle"] -marker = "sys_platform == \"win32\" and python_version < \"3.13\" and (python_version >= \"3.9\" or python_version == \"3.8\")" +marker = "sys_platform == \"win32\" and python_version < \"3.13\"" 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"}, @@ -1377,8 +1125,8 @@ files = [ [[package]] name = "requests" -version = "2.32.4" -requires_python = ">=3.8" +version = "2.32.5" +requires_python = ">=3.9" summary = "Python HTTP for Humans." groups = ["default", "doc"] dependencies = [ @@ -1388,8 +1136,8 @@ dependencies = [ "urllib3<3,>=1.21.1", ] files = [ - {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, - {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, ] [[package]] @@ -1434,7 +1182,6 @@ name = "sadisplay" version = "0.4.9" summary = "SqlAlchemy schema display script" groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "SQLAlchemy>=0.5", ] @@ -1460,24 +1207,11 @@ version = "79.0.1" requires_python = ">=3.9" summary = "Easily download, build, install, upgrade, and uninstall Python packages" groups = ["default", "bundle", "lint", "release"] -marker = "python_version >= \"3.9\"" files = [ {file = "setuptools-79.0.1-py3-none-any.whl", hash = "sha256:e147c0549f27767ba362f9da434eab9c5dc0045d5304feb602a0af001089fc51"}, {file = "setuptools-79.0.1.tar.gz", hash = "sha256:128ce7b8f33c3079fd1b067ecbb4051a66e8526e7b65f6cec075dfc650ddfa88"}, ] -[[package]] -name = "setuptools" -version = "75.3.2" -requires_python = ">=3.8" -summary = "Easily download, build, install, upgrade, and uninstall Python packages" -groups = ["default", "bundle", "lint", "release"] -marker = "python_version == \"3.8\"" -files = [ - {file = "setuptools-75.3.2-py3-none-any.whl", hash = "sha256:90ab613b6583fc02d5369cbca13ea26ea0e182d1df2d943ee9cbe81d4c61add9"}, - {file = "setuptools-75.3.2.tar.gz", hash = "sha256:3c1383e1038b68556a382c1e8ded8887cd20141b0eb5708a6c8d277de49364f5"}, -] - [[package]] name = "six" version = "1.17.0" @@ -1495,7 +1229,6 @@ version = "1.3.1" requires_python = ">=3.7" summary = "Sniff out which async library your code is running under" groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -1507,7 +1240,6 @@ version = "3.0.1" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*" summary = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, @@ -1519,7 +1251,6 @@ version = "7.3.7" requires_python = ">=3.9" summary = "Python documentation generator" groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "Jinja2>=3.0", "Pygments>=2.14", @@ -1551,7 +1282,6 @@ version = "0.4.0" requires_python = ">=3.7" summary = "A sphinx extension that automatically documents argparse commands and options" groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "sphinx>=1.2.0", ] @@ -1566,7 +1296,6 @@ version = "2024.10.3" requires_python = ">=3.9" summary = "Rebuild Sphinx documentation on changes, with hot reloading in the browser." groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "colorama>=0.4.6", "sphinx", @@ -1586,7 +1315,6 @@ version = "1.3.0" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" summary = "Read the Docs theme for Sphinx" groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "docutils<0.19", "sphinx<8,>=1.6", @@ -1603,7 +1331,6 @@ version = "2.0.0" requires_python = ">=3.9" summary = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, @@ -1615,7 +1342,6 @@ version = "2.0.0" requires_python = ">=3.9" summary = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, @@ -1627,7 +1353,6 @@ version = "2.1.0" requires_python = ">=3.9" summary = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, @@ -1639,7 +1364,6 @@ version = "4.1" requires_python = ">=2.7" summary = "Extension to include jQuery on newer Sphinx releases" groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "Sphinx>=1.8", ] @@ -1654,7 +1378,6 @@ version = "1.0.1" requires_python = ">=3.5" summary = "A sphinx extension which renders display math in HTML via JavaScript" groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, @@ -1665,7 +1388,6 @@ name = "sphinxcontrib-plantuml" version = "0.30" summary = "Sphinx \"plantuml\" extension" groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "Sphinx>=1.6", ] @@ -1679,7 +1401,6 @@ version = "2.0.0" requires_python = ">=3.9" summary = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, @@ -1691,7 +1412,6 @@ version = "2.0.0" requires_python = ">=3.9" summary = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, @@ -1699,59 +1419,58 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.42" +version = "2.0.43" requires_python = ">=3.7" summary = "Database Abstraction Library" groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "greenlet>=1; (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\") and python_version < \"3.14\"", "importlib-metadata; python_version < \"3.8\"", "typing-extensions>=4.6.0", ] files = [ - {file = "sqlalchemy-2.0.42-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:172b244753e034d91a826f80a9a70f4cbac690641207f2217f8404c261473efe"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be28f88abd74af8519a4542185ee80ca914933ca65cdfa99504d82af0e4210df"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98b344859d282fde388047f1710860bb23f4098f705491e06b8ab52a48aafea9"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97978d223b11f1d161390a96f28c49a13ce48fdd2fed7683167c39bdb1b8aa09"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e35b9b000c59fcac2867ab3a79fc368a6caca8706741beab3b799d47005b3407"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bc7347ad7a7b1c78b94177f2d57263113bb950e62c59b96ed839b131ea4234e1"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-win32.whl", hash = "sha256:739e58879b20a179156b63aa21f05ccacfd3e28e08e9c2b630ff55cd7177c4f1"}, - {file = "sqlalchemy-2.0.42-cp310-cp310-win_amd64.whl", hash = "sha256:1aef304ada61b81f1955196f584b9e72b798ed525a7c0b46e09e98397393297b"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c34100c0b7ea31fbc113c124bcf93a53094f8951c7bf39c45f39d327bad6d1e7"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad59dbe4d1252448c19d171dfba14c74e7950b46dc49d015722a4a06bfdab2b0"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9187498c2149919753a7fd51766ea9c8eecdec7da47c1b955fa8090bc642eaa"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f092cf83ebcafba23a247f5e03f99f5436e3ef026d01c8213b5eca48ad6efa9"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc6afee7e66fdba4f5a68610b487c1f754fccdc53894a9567785932dbb6a265e"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:260ca1d2e5910f1f1ad3fe0113f8fab28657cee2542cb48c2f342ed90046e8ec"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-win32.whl", hash = "sha256:2eb539fd83185a85e5fcd6b19214e1c734ab0351d81505b0f987705ba0a1e231"}, - {file = "sqlalchemy-2.0.42-cp311-cp311-win_amd64.whl", hash = "sha256:9193fa484bf00dcc1804aecbb4f528f1123c04bad6a08d7710c909750fa76aeb"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:09637a0872689d3eb71c41e249c6f422e3e18bbd05b4cd258193cfc7a9a50da2"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3cb3ec67cc08bea54e06b569398ae21623534a7b1b23c258883a7c696ae10df"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e87e6a5ef6f9d8daeb2ce5918bf5fddecc11cae6a7d7a671fcc4616c47635e01"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b718011a9d66c0d2f78e1997755cd965f3414563b31867475e9bc6efdc2281d"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:16d9b544873fe6486dddbb859501a07d89f77c61d29060bb87d0faf7519b6a4d"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21bfdf57abf72fa89b97dd74d3187caa3172a78c125f2144764a73970810c4ee"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-win32.whl", hash = "sha256:78b46555b730a24901ceb4cb901c6b45c9407f8875209ed3c5d6bcd0390a6ed1"}, - {file = "sqlalchemy-2.0.42-cp312-cp312-win_amd64.whl", hash = "sha256:4c94447a016f36c4da80072e6c6964713b0af3c8019e9c4daadf21f61b81ab53"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:941804f55c7d507334da38133268e3f6e5b0340d584ba0f277dd884197f4ae8c"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d3d06a968a760ce2aa6a5889fefcbdd53ca935735e0768e1db046ec08cbf01"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cf10396a8a700a0f38ccd220d940be529c8f64435c5d5b29375acab9267a6c9"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cae6c2b05326d7c2c7c0519f323f90e0fb9e8afa783c6a05bb9ee92a90d0f04"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f50f7b20677b23cfb35b6afcd8372b2feb348a38e3033f6447ee0704540be894"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d88a1c0d66d24e229e3938e1ef16ebdbd2bf4ced93af6eff55225f7465cf350"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-win32.whl", hash = "sha256:45c842c94c9ad546c72225a0c0d1ae8ef3f7c212484be3d429715a062970e87f"}, - {file = "sqlalchemy-2.0.42-cp313-cp313-win_amd64.whl", hash = "sha256:eb9905f7f1e49fd57a7ed6269bc567fcbbdac9feadff20ad6bd7707266a91577"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:78548fd65cd76d4c5a2e6b5f245d7734023ee4de33ee7bb298f1ac25a9935e0d"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cf4bf5a174d8a679a713b7a896470ffc6baab78e80a79e7ec5668387ffeccc8b"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c7ff7ba08b375f8a8fa0511e595c9bdabb5494ec68f1cf69bb24e54c0d90f2"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b3c117f65d64e806ce5ce9ce578f06224dc36845e25ebd2554b3e86960e1aed"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:27e4a7b3a7a61ff919c2e7caafd612f8626114e6e5ebbe339de3b5b1df9bc27e"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b01e0dd39f96aefda5ab002d8402db4895db871eb0145836246ce0661635ce55"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-win32.whl", hash = "sha256:49362193b1f43aa158deebf438062d7b5495daa9177c6c5d0f02ceeb64b544ea"}, - {file = "sqlalchemy-2.0.42-cp39-cp39-win_amd64.whl", hash = "sha256:636ec3dc83b2422a7ff548d0f8abf9c23742ca50e2a5cdc492a151eac7a0248b"}, - {file = "sqlalchemy-2.0.42-py3-none-any.whl", hash = "sha256:defcdff7e661f0043daa381832af65d616e060ddb54d3fe4476f51df7eaa1835"}, - {file = "sqlalchemy-2.0.42.tar.gz", hash = "sha256:160bedd8a5c28765bd5be4dec2d881e109e33b34922e50a3b881a7681773ac5f"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70322986c0c699dca241418fcf18e637a4369e0ec50540a2b907b184c8bca069"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87accdbba88f33efa7b592dc2e8b2a9c2cdbca73db2f9d5c510790428c09c154"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c00e7845d2f692ebfc7d5e4ec1a3fd87698e4337d09e58d6749a16aedfdf8612"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:022e436a1cb39b13756cf93b48ecce7aa95382b9cfacceb80a7d263129dfd019"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c5e73ba0d76eefc82ec0219d2301cb33bfe5205ed7a2602523111e2e56ccbd20"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9c2e02f06c68092b875d5cbe4824238ab93a7fa35d9c38052c033f7ca45daa18"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-win32.whl", hash = "sha256:e7a903b5b45b0d9fa03ac6a331e1c1d6b7e0ab41c63b6217b3d10357b83c8b00"}, + {file = "sqlalchemy-2.0.43-cp310-cp310-win_amd64.whl", hash = "sha256:4bf0edb24c128b7be0c61cd17eef432e4bef507013292415f3fb7023f02b7d4b"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52d9b73b8fb3e9da34c2b31e6d99d60f5f99fd8c1225c9dad24aeb74a91e1d29"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f42f23e152e4545157fa367b2435a1ace7571cab016ca26038867eb7df2c3631"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fb1a8c5438e0c5ea51afe9c6564f951525795cf432bed0c028c1cb081276685"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db691fa174e8f7036afefe3061bc40ac2b770718be2862bfb03aabae09051aca"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2b3b4927d0bc03d02ad883f402d5de201dbc8894ac87d2e981e7d87430e60d"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d3d9b904ad4a6b175a2de0738248822f5ac410f52c2fd389ada0b5262d6a1e3"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-win32.whl", hash = "sha256:5cda6b51faff2639296e276591808c1726c4a77929cfaa0f514f30a5f6156921"}, + {file = "sqlalchemy-2.0.43-cp311-cp311-win_amd64.whl", hash = "sha256:c5d1730b25d9a07727d20ad74bc1039bbbb0a6ca24e6769861c1aa5bf2c4c4a8"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:20d81fc2736509d7a2bd33292e489b056cbae543661bb7de7ce9f1c0cd6e7f24"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b9fc27650ff5a2c9d490c13c14906b918b0de1f8fcbb4c992712d8caf40e83"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6772e3ca8a43a65a37c88e2f3e2adfd511b0b1da37ef11ed78dea16aeae85bd9"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a113da919c25f7f641ffbd07fbc9077abd4b3b75097c888ab818f962707eb48"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4286a1139f14b7d70141c67a8ae1582fc2b69105f1b09d9573494eb4bb4b2687"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:529064085be2f4d8a6e5fab12d36ad44f1909a18848fcfbdb59cc6d4bbe48efe"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-win32.whl", hash = "sha256:b535d35dea8bbb8195e7e2b40059e2253acb2b7579b73c1b432a35363694641d"}, + {file = "sqlalchemy-2.0.43-cp312-cp312-win_amd64.whl", hash = "sha256:1c6d85327ca688dbae7e2b06d7d84cfe4f3fffa5b5f9e21bb6ce9d0e1a0e0e0a"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-win32.whl", hash = "sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d"}, + {file = "sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl", hash = "sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ceb5c832cc30663aeaf5e39657712f4c4241ad1f638d487ef7216258f6d41fe7"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11f43c39b4b2ec755573952bbcc58d976779d482f6f832d7f33a8d869ae891bf"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:413391b2239db55be14fa4223034d7e13325a1812c8396ecd4f2c08696d5ccad"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c379e37b08c6c527181a397212346be39319fb64323741d23e46abd97a400d34"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03d73ab2a37d9e40dec4984d1813d7878e01dbdc742448d44a7341b7a9f408c7"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8cee08f15d9e238ede42e9bbc1d6e7158d0ca4f176e4eab21f88ac819ae3bd7b"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-win32.whl", hash = "sha256:b3edaec7e8b6dc5cd94523c6df4f294014df67097c8217a89929c99975811414"}, + {file = "sqlalchemy-2.0.43-cp39-cp39-win_amd64.whl", hash = "sha256:227119ce0a89e762ecd882dc661e0aa677a690c914e358f0dd8932a2e8b2765b"}, + {file = "sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc"}, + {file = "sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417"}, ] [[package]] @@ -1760,7 +1479,6 @@ version = "0.47.2" requires_python = ">=3.9" summary = "The little ASGI library that shines." groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "anyio<5,>=3.6.2", "typing-extensions>=4.10.0; python_version < \"3.13\"", @@ -1893,26 +1611,13 @@ files = [ [[package]] name = "types-python-dateutil" -version = "2.9.0.20250708" +version = "2.9.0.20250809" requires_python = ">=3.9" summary = "Typing stubs for python-dateutil" groups = ["default"] -marker = "python_version >= \"3.9\"" -files = [ - {file = "types_python_dateutil-2.9.0.20250708-py3-none-any.whl", hash = "sha256:4d6d0cc1cc4d24a2dc3816024e502564094497b713f7befda4d5bc7a8e3fd21f"}, - {file = "types_python_dateutil-2.9.0.20250708.tar.gz", hash = "sha256:ccdbd75dab2d6c9696c350579f34cffe2c281e4c5f27a585b2a2438dd1d5c8ab"}, -] - -[[package]] -name = "types-python-dateutil" -version = "2.9.0.20241206" -requires_python = ">=3.8" -summary = "Typing stubs for python-dateutil" -groups = ["default"] -marker = "python_version == \"3.8\"" files = [ - {file = "types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53"}, - {file = "types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb"}, + {file = "types_python_dateutil-2.9.0.20250809-py3-none-any.whl", hash = "sha256:768890cac4f2d7fd9e0feb6f3217fce2abbfdfc0cadd38d11fba325a815e4b9f"}, + {file = "types_python_dateutil-2.9.0.20250809.tar.gz", hash = "sha256:69cbf8d15ef7a75c3801d65d63466e46ac25a0baa678d89d0a137fc31a608cc1"}, ] [[package]] @@ -1921,31 +1626,17 @@ version = "4.14.1" requires_python = ">=3.9" summary = "Backported and Experimental Type Hints for Python 3.9+" groups = ["default", "doc", "full", "lint", "test"] -marker = "python_version >= \"3.9\"" files = [ {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] -[[package]] -name = "typing-extensions" -version = "4.13.2" -requires_python = ">=3.8" -summary = "Backported and Experimental Type Hints for Python 3.8+" -groups = ["default", "full", "lint", "test"] -marker = "python_version == \"3.8\"" -files = [ - {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, - {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, -] - [[package]] name = "typing-inspection" version = "0.4.1" requires_python = ">=3.9" summary = "Runtime typing introspection tools" groups = ["full"] -marker = "python_version >= \"3.9\"" dependencies = [ "typing-extensions>=4.12.0", ] @@ -1960,31 +1651,17 @@ version = "2.5.0" requires_python = ">=3.9" summary = "HTTP library with thread-safe connection pooling, file post, and more." groups = ["default", "doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] -[[package]] -name = "urllib3" -version = "2.2.3" -requires_python = ">=3.8" -summary = "HTTP library with thread-safe connection pooling, file post, and more." -groups = ["default"] -marker = "python_version == \"3.8\"" -files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, -] - [[package]] name = "uvicorn" version = "0.35.0" requires_python = ">=3.9" summary = "The lightning-fast ASGI server." groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "click>=7.0", "h11>=0.8", @@ -2001,7 +1678,6 @@ version = "6.0.0" requires_python = ">=3.9" summary = "Filesystem events monitoring" groups = ["test"] -marker = "python_version >= \"3.9\"" files = [ {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, @@ -2035,39 +1711,12 @@ files = [ {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, ] -[[package]] -name = "watchdog" -version = "4.0.2" -requires_python = ">=3.8" -summary = "Filesystem events monitoring" -groups = ["test"] -marker = "python_version == \"3.8\"" -files = [ - {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"}, - {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"}, - {file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"}, - {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"}, - {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"}, - {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"}, - {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"}, - {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"}, - {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"}, -] - [[package]] name = "watchfiles" version = "1.1.0" requires_python = ">=3.9" summary = "Simple, modern and high performance file watching and code reload in python." groups = ["doc"] -marker = "python_version >= \"3.9\"" dependencies = [ "anyio>=3.0.0", ] @@ -2199,7 +1848,6 @@ version = "15.0.1" requires_python = ">=3.9" summary = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" groups = ["doc"] -marker = "python_version >= \"3.9\"" files = [ {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, @@ -2278,20 +1926,8 @@ version = "3.23.0" requires_python = ">=3.9" summary = "Backport of pathlib-compatible object wrapper for zip files" groups = ["bundle", "doc", "release"] -marker = "python_version < \"3.10\" and python_version >= \"3.9\"" +marker = "python_version < \"3.10\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] - -[[package]] -name = "zipp" -version = "3.20.2" -requires_python = ">=3.8" -summary = "Backport of pathlib-compatible object wrapper for zip files" -groups = ["bundle", "release"] -marker = "python_version == \"3.8\"" -files = [ - {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, - {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, -] diff --git a/pyproject.toml b/pyproject.toml index e1c83b0bb..10f1290a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ authors = [ { name = "Backblaze Inc", email = "support@backblaze.com" }, ] dynamic = ["version"] -requires-python = ">=3.8" +requires-python = ">=3.9" keywords = ["backblaze b2 cloud storage"] license = {text = "MIT"} readme = "README.md" @@ -15,7 +15,6 @@ classifiers = [ "Topic :: Software Development :: Libraries", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -25,10 +24,9 @@ classifiers = [ dependencies = [ "argcomplete>=3.5.2,<4", "arrow>=1.0.2,<2.0.0", - "b2sdk>=2.9.4,<3", + "b2sdk>=2.10.0,<3", "docutils>=0.18.1,<0.22", "idna~=3.4; platform_system == 'Java'", - "importlib-metadata>=3.3; python_version < '3.8'", "phx-class-registry>=4.0,<5", "rst2ansi==0.1.5", "tabulate==0.9.0", @@ -42,20 +40,18 @@ dependencies = [ # requirements. They should be removed from this section when # a breaking version is released. doc = [ - "sadisplay>=0.4.9; python_version >= '3.9'", - "sphinx>=7.2,<8; python_version >= '3.9'", - "sphinx-argparse; python_version >= '3.9'", - "sphinx-autobuild; python_version >= '3.9'", - "sphinx-rtd-theme>=1.3,<2; python_version >= '3.9'", - "sphinxcontrib-plantuml; python_version >= '3.9'" + "sadisplay>=0.4.9", + "sphinx>=7.2,<8", + "sphinx-argparse", + "sphinx-autobuild", + "sphinx-rtd-theme>=1.3,<2", + "sphinxcontrib-plantuml" ] license = [ "pip>=23.1.0", - "pip-licenses==3.5.5; python_version < '3.9'", - "pip-licenses~=5.0; python_version >= '3.9'", - "pipdeptree>=2.9,<3; python_version >= '3.9'", - "prettytable~=3.7; python_version < '3.9'", - "prettytable~=3.9; python_version >= '3.9'", + "pip-licenses~=5.0", + "pipdeptree>=2.9,<3", + "prettytable~=3.9", ] full = [ "pydantic>=2.0.1,<3" @@ -115,7 +111,7 @@ optional_dependencies = ["doc", "full", "license"] [tool.ruff] line-length = 100 -target-version = "py38" +target-version = "py39" [tool.ruff.lint] # TODO add D @@ -204,7 +200,7 @@ lint = [ "setuptools>=60,<80", # required by liccheck ] release = [ - "towncrier==23.11.0; python_version >= '3.8'", + "towncrier==23.11.0", ] test = [ "coverage==7.2.7", diff --git a/test/conftest.py b/test/conftest.py index 080e01d30..8f0629b9c 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -12,8 +12,6 @@ import pytest -from b2._internal._utils.python_compat import removeprefix - @pytest.hookimpl def pytest_configure(config): @@ -26,7 +24,7 @@ def pytest_configure(config): @pytest.fixture(scope='session') def apiver(request): """Get apiver as a v-prefixed string, e.g. "v2".""" - return removeprefix(request.config.getoption('--cli', '').lstrip('_'), 'b2') or None + return request.config.getoption('--cli', '').lstrip('_').removeprefix('b2') or None @pytest.fixture(scope='session') diff --git a/test/integration/cleanup_buckets.py b/test/integration/cleanup_buckets.py index 51d216528..29ea001db 100644 --- a/test/integration/cleanup_buckets.py +++ b/test/integration/cleanup_buckets.py @@ -9,7 +9,7 @@ ###################################################################### -def test_cleanup_buckets(b2_api): +def test_cleanup_buckets(bucket_manager): # this is not a test, but it is intended to be called # via pytest because it reuses fixtures which have everything # set up diff --git a/test/integration/conftest.py b/test/integration/conftest.py index e6846e85e..e2c7f99a1 100755 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -17,12 +17,13 @@ import sys import tempfile import uuid +from collections.abc import Generator from os import environ, path from tempfile import TemporaryDirectory -from typing import Generator import pytest -from b2sdk.v3 import B2_ACCOUNT_INFO_ENV_VAR, XDG_CONFIG_HOME_ENV_VAR, Bucket +from b2sdk.v3 import B2_ACCOUNT_INFO_ENV_VAR, XDG_CONFIG_HOME_ENV_VAR +from b2sdk.v3.testing import NODE_DESCRIPTION, RNG_SEED, random_token from b2._internal.version_listing import ( CLI_VERSIONS, @@ -32,7 +33,7 @@ ) from ..helpers import b2_uri_args_v3, b2_uri_args_v4 -from .helpers import NODE_DESCRIPTION, RNG_SEED, Api, CommandLine, bucket_name_part, random_token +from .helpers import CommandLine from .persistent_bucket import ( PersistentBucketAggregate, get_or_create_persistent_bucket, @@ -41,9 +42,12 @@ logger = logging.getLogger(__name__) -GENERAL_BUCKET_NAME_PREFIX = 'clitst' TEMPDIR = tempfile.gettempdir() ROOT_PATH = pathlib.Path(__file__).parent.parent.parent +GENERAL_BUCKET_NAME_PREFIX = 'clitst' + + +pytest_plugins = ['b2sdk.v3.testing'] @pytest.fixture(scope='session', autouse=True) @@ -145,37 +149,13 @@ def cli_version(request) -> str: @pytest.fixture(scope='session') -def application_key() -> str: - key = environ.get('B2_TEST_APPLICATION_KEY') - assert application_key, 'B2_TEST_APPLICATION_KEY is not set' - yield key - - -@pytest.fixture(scope='session') -def application_key_id() -> str: - key_id = environ.get('B2_TEST_APPLICATION_KEY_ID') - assert key_id, 'B2_TEST_APPLICATION_KEY_ID is not set' - yield key_id +def application_key(b2_auth_data) -> str: + yield b2_auth_data[1] @pytest.fixture(scope='session') -def realm() -> str: - yield environ.get('B2_TEST_ENVIRONMENT', 'production') - - -@pytest.fixture -def bucket(bucket_factory) -> Bucket: - return bucket_factory() - - -@pytest.fixture -def bucket_factory(b2_api, schedule_bucket_cleanup): - def create_bucket(**kwargs): - new_bucket = b2_api.create_bucket(**kwargs) - schedule_bucket_cleanup(new_bucket.name, new_bucket.bucket_dict) - return new_bucket - - yield create_bucket +def application_key_id(b2_auth_data) -> str: + yield b2_auth_data[0] @pytest.fixture(scope='function') @@ -190,26 +170,21 @@ def file_name(bucket) -> str: @pytest.fixture(scope='function') # , autouse=True) -def debug_print_buckets(b2_api): +def debug_print_buckets(bucket_manager): print('-' * 30) print('Buckets before test ' + environ['PYTEST_CURRENT_TEST']) - num_buckets = b2_api.count_and_print_buckets() + num_buckets = bucket_manager.count_and_print_buckets() print('-' * 30) try: yield finally: print('-' * 30) print('Buckets after test ' + environ['PYTEST_CURRENT_TEST']) - delta = b2_api.count_and_print_buckets() - num_buckets + delta = bucket_manager.count_and_print_buckets() - num_buckets print(f'DELTA: {delta}') print('-' * 30) -@pytest.fixture(scope='session') -def this_run_bucket_name_prefix() -> str: - yield GENERAL_BUCKET_NAME_PREFIX + bucket_name_part(8) - - @pytest.fixture(scope='session') def monkeysession(): with pytest.MonkeyPatch.context() as mp: @@ -242,26 +217,25 @@ def auto_change_account_info_dir(monkeysession) -> str: @pytest.fixture(scope='session') -def b2_api( +def general_bucket_name_prefix(): + return GENERAL_BUCKET_NAME_PREFIX + + +@pytest.fixture(scope='session') +def bucket_manager( + bucket_manager, application_key_id, application_key, realm, - this_run_bucket_name_prefix, auto_change_account_info_dir, summary_notes, -) -> Api: - api = Api( - application_key_id, - application_key, - realm, - general_bucket_name_prefix=GENERAL_BUCKET_NAME_PREFIX, - this_run_bucket_name_prefix=this_run_bucket_name_prefix, - ) - yield api - api.clean_buckets() +): + yield bucket_manager # showing account_id in the logs is safe; so we explicitly prevent it from being redacted - summary_notes.append(f'B2 Account ID: {api.account_id[:1]!r}{api.account_id[1:]!r}') - summary_notes.append(f'Buckets names used during this tests: {api.bucket_name_log!r}') + summary_notes.append(f'B2 Account ID: {application_key_id[:1]!r}{application_key_id[1:]!r}') + summary_notes.append( + f'Buckets names used during this tests: {bucket_manager.bucket_name_log!r}' + ) @pytest.fixture(scope='module') @@ -270,8 +244,8 @@ def global_b2_tool( application_key_id, application_key, realm, - this_run_bucket_name_prefix, - b2_api, + bucket_name_prefix, + bucket_manager, auto_change_account_info_dir, b2_uri_args, ) -> CommandLine: @@ -280,9 +254,9 @@ def global_b2_tool( application_key_id, application_key, realm, - this_run_bucket_name_prefix, + bucket_name_prefix, request.config.getoption('--env-file-cmd-placeholder'), - api_wrapper=b2_api, + bucket_manager=bucket_manager, b2_uri_args=b2_uri_args, ) tool.reauthorize(check_key_capabilities=True) # reauthorize for the first time (with check) @@ -306,7 +280,8 @@ def schedule_bucket_cleanup(global_b2_tool): """ Explicitly ask for buckets cleanup after the test - This should be only used when testing `bucket create` command; otherwise use `bucket_factory` fixture. + This should be only used when testing `bucket create` command; + otherwise use `b2sdk.v3.testing.IntegrationTestBase.create_bucket()`. """ buckets_to_clean = {} @@ -429,10 +404,10 @@ def b2_uri_args(apiver_int): @pytest.fixture(scope='session') -def base_persistent_bucket(b2_api): - bucket = get_or_create_persistent_bucket(b2_api) +def base_persistent_bucket(bucket_manager): + bucket = get_or_create_persistent_bucket(bucket_manager) yield bucket - prune_used_files(b2_api=b2_api, bucket=bucket, folders=subfolder_list) + prune_used_files(bucket_manager=bucket_manager, bucket=bucket, folders=subfolder_list) @pytest.fixture diff --git a/test/integration/helpers.py b/test/integration/helpers.py index f35c99f95..2dcb7a207 100755 --- a/test/integration/helpers.py +++ b/test/integration/helpers.py @@ -9,94 +9,39 @@ ###################################################################### from __future__ import annotations -import dataclasses import json import logging import os import pathlib import platform -import random import re -import secrets import shutil -import string import subprocess import sys import threading -import time import warnings -from dataclasses import dataclass -from datetime import datetime, timedelta -from hashlib import sha256 +from collections.abc import Iterable from os import environ, linesep from pathlib import Path from tempfile import mkdtemp, mktemp -from typing import Any, Iterable, TypeVar +from typing import TypeVar -import tenacity from b2sdk.v3 import ( ALL_CAPABILITIES, - BUCKET_NAME_CHARS_UNIQ, - BUCKET_NAME_LENGTH_RANGE, - NO_RETENTION_FILE_SETTING, - B2Api, - Bucket, EncryptionAlgorithm, EncryptionKey, EncryptionMode, EncryptionSetting, - InMemoryAccountInfo, - InMemoryCache, - LegalHold, - RetentionMode, fix_windows_path_limit, ) -from b2sdk.v3.exception import ( - BadRequest, - BucketIdNotFound, - FileNotPresent, - TooManyRequests, -) +from b2sdk.v3.testing import ONE_HOUR_MILLIS, RNG, BucketManager -from b2._internal.console_tool import Command, current_time_millis +from b2._internal.console_tool import Command logger = logging.getLogger(__name__) -BUCKET_CLEANUP_PERIOD_MILLIS = timedelta(hours=3).total_seconds() * 1000 -ONE_HOUR_MILLIS = 60 * 60 * 1000 ONE_DAY_MILLIS = ONE_HOUR_MILLIS * 24 -BUCKET_NAME_LENGTH = BUCKET_NAME_LENGTH_RANGE[1] -BUCKET_CREATED_AT_MILLIS = 'created_at_millis' - -NODE_DESCRIPTION = f'{platform.node()}: {platform.platform()} {platform.python_version()}' - - -def get_seed() -> str: - """ - Get seed for random number generator. - - The `WORKFLOW_ID` variable has to be set in the CI to uniquely identify - the current workflow (including the attempt) - """ - seed = ''.join( - ( - os.getenv('WORKFLOW_ID', secrets.token_hex(8)), - NODE_DESCRIPTION, - str(time.time_ns()), - os.getenv('PYTEST_XDIST_WORKER', 'gw0'), - ) - ) - return sha256(seed.encode()).hexdigest()[:16] - - -RNG_SEED = get_seed() -RNG = random.Random(RNG_SEED) -RNG_COUNTER = 0 - -if sys.version_info < (3, 9): - RNG.randbytes = lambda n: RNG.getrandbits(n * 8).to_bytes(n, 'little') - SSE_NONE = EncryptionSetting( mode=EncryptionMode.NONE, ) @@ -117,22 +62,6 @@ def get_seed() -> str: ) -def random_token(length: int, chars=string.ascii_letters) -> str: - return ''.join(RNG.choice(chars) for _ in range(length)) - - -def bucket_name_part(length: int) -> str: - assert length >= 1 - global RNG_COUNTER - RNG_COUNTER += 1 - name_part = random_token(length, BUCKET_NAME_CHARS_UNIQ) - logger.info('RNG_SEED: %s', RNG_SEED) - logger.info('RNG_COUNTER: %i, length: %i', RNG_COUNTER, length) - logger.info('name_part: %s', name_part) - logger.info('WORKFLOW_ID: %s', os.getenv('WORKFLOW_ID')) - return name_part - - T = TypeVar('T') @@ -141,196 +70,6 @@ def wrap_iterables(generators: list[Iterable[T]]): yield from g -@dataclass -class Api: - account_id: str - application_key: str - realm: str - general_bucket_name_prefix: str - this_run_bucket_name_prefix: str - - api: B2Api = None - bucket_name_log: list[str] = dataclasses.field(default_factory=list) - - def __post_init__(self): - info = InMemoryAccountInfo() - cache = InMemoryCache() - self.api = B2Api(info, cache=cache) - self.api.authorize_account(self.account_id, self.application_key, realm=self.realm) - assert ( - BUCKET_NAME_LENGTH - len(self.this_run_bucket_name_prefix) > 5 - ), self.this_run_bucket_name_prefix - - def new_bucket_name(self) -> str: - bucket_name = self.this_run_bucket_name_prefix + bucket_name_part( - BUCKET_NAME_LENGTH - len(self.this_run_bucket_name_prefix) - ) - self.bucket_name_log.append(bucket_name) - return bucket_name - - def new_bucket_info(self) -> dict: - return { - BUCKET_CREATED_AT_MILLIS: str(current_time_millis()), - 'created_by': NODE_DESCRIPTION, - } - - def create_bucket(self, bucket_type: str = 'allPublic', **kwargs) -> Bucket: - bucket_name = self.new_bucket_name() - return self.api.create_bucket( - bucket_name, - bucket_type=bucket_type, - bucket_info=self.new_bucket_info(), - **kwargs, - ) - - def _should_remove_bucket(self, bucket: Bucket) -> tuple[bool, str]: - if bucket.name.startswith(self.this_run_bucket_name_prefix): - return True, 'it is a bucket for this very run' - if bucket.name.startswith(self.general_bucket_name_prefix): - if BUCKET_CREATED_AT_MILLIS in bucket.bucket_info: - delete_older_than = current_time_millis() - BUCKET_CLEANUP_PERIOD_MILLIS - this_bucket_creation_time = int(bucket.bucket_info[BUCKET_CREATED_AT_MILLIS]) - if this_bucket_creation_time < delete_older_than: - return ( - True, - f'this_bucket_creation_time={this_bucket_creation_time} < delete_older_than={delete_older_than}', - ) - return ( - False, - f'this_bucket_creation_time={this_bucket_creation_time} >= delete_older_than={delete_older_than}', - ) - else: - return True, 'undefined ' + BUCKET_CREATED_AT_MILLIS - return False, f'does not start with {self.general_bucket_name_prefix!r}' - - def clean_buckets(self, quick=False): - # even with use_cache=True, if cache is empty API call will be made - buckets = self.api.list_buckets(use_cache=quick) - remaining_buckets = [] - for bucket in buckets: - should_remove, why = self._should_remove_bucket(bucket) - if not should_remove: - print(f'Skipping bucket removal {bucket.name!r} because {why}') - remaining_buckets.append(bucket) - continue - - print('Trying to remove bucket:', bucket.name, 'because', why) - try: - self.clean_bucket(bucket) - except BucketIdNotFound: - print(f'It seems that bucket {bucket.name} has already been removed') - print('Total bucket count after cleanup:', len(remaining_buckets)) - for bucket in remaining_buckets: - print(bucket) - - @tenacity.retry( - retry=tenacity.retry_if_exception_type(TooManyRequests), - wait=tenacity.wait_exponential(), - stop=tenacity.stop_after_attempt(8), - ) - def clean_bucket( - self, - bucket_object: Bucket | str, - only_files: bool = False, - only_folders: list[str] | None = None, - ignore_retentions: bool = False, - ): - """ - Clean contents of bucket, by default also deleting the bucket. - - Args: - bucket (Bucket | str): Bucket object or name - only_files (bool): If to only delete files and not the bucket - only_folders (list[str] | None): If not None, filter to only files in given folders. - ignore_retentions (bool): If deletion should happen regardless of files' retention mode. - """ - bucket: Bucket - if isinstance(bucket_object, str): - bucket = self.api.get_bucket_by_name(bucket_object) - else: - bucket = bucket_object - - if not only_files: - # try optimistic bucket removal first, since it is completely free (as opposed to `ls` call) - try: - return self.api.delete_bucket(bucket) - except BucketIdNotFound: - return # bucket was already removed - except BadRequest as exc: - assert exc.code == 'cannot_delete_non_empty_bucket', exc.code - - files_leftover = False - - file_versions: Iterable[Any] - if only_folders: - file_versions = wrap_iterables( - [ - bucket.ls( - path=folder, - latest_only=False, - recursive=True, - ) - for folder in only_folders - ] - ) - else: - file_versions = bucket.ls(latest_only=False, recursive=True) - - for file_version_info, _ in file_versions: - if file_version_info.file_retention and not ignore_retentions: - if file_version_info.file_retention.mode == RetentionMode.GOVERNANCE: - print('Removing retention from file version:', file_version_info.id_) - self.api.update_file_retention( - file_version_info.id_, - file_version_info.file_name, - NO_RETENTION_FILE_SETTING, - True, - ) - elif file_version_info.file_retention.mode == RetentionMode.COMPLIANCE: - if file_version_info.file_retention.retain_until > current_time_millis(): - print( - f'File version: {file_version_info.id_} cannot be removed due to compliance mode retention' - ) - files_leftover = True - continue - elif file_version_info.file_retention.mode == RetentionMode.NONE: - pass - else: - raise ValueError( - f'Unknown retention mode: {file_version_info.file_retention.mode}' - ) - if file_version_info.legal_hold.is_on(): - print('Removing legal hold from file version:', file_version_info.id_) - self.api.update_file_legal_hold( - file_version_info.id_, file_version_info.file_name, LegalHold.OFF - ) - print('Removing file version:', file_version_info.id_) - try: - self.api.delete_file_version(file_version_info.id_, file_version_info.file_name) - except FileNotPresent: - print( - f'It seems that file version {file_version_info.id_} has already been removed' - ) - - if files_leftover: - print('Unable to remove bucket because some retained files remain') - elif not only_files: - print('Removing bucket:', bucket.name) - try: - self.api.delete_bucket(bucket) - except BucketIdNotFound: - print(f'It seems that bucket {bucket.name} has already been removed') - print() - - def count_and_print_buckets(self) -> int: - buckets = self.api.list_buckets() - count = len(buckets) - print(f'Total bucket count at {datetime.now()}: {count}') - for i, bucket in enumerate(buckets, start=1): - print(f'- {i}\t{bucket.name} [{bucket.id_}]') - return count - - def print_text_indented(text): """ Prints text that may include weird characters, indented four spaces. @@ -418,7 +157,7 @@ def __init__( realm, bucket_name_prefix, env_file_cmd_placeholder, - api_wrapper: Api, + bucket_manager: BucketManager, b2_uri_args, ): self.command = command @@ -427,14 +166,14 @@ def __init__( self.realm = realm self.bucket_name_prefix = bucket_name_prefix self.env_file_cmd_placeholder = env_file_cmd_placeholder - self.api_wrapper = api_wrapper + self.bucket_manager = bucket_manager self.b2_uri_args = b2_uri_args def generate_bucket_name(self): - return self.api_wrapper.new_bucket_name() + return self.bucket_manager.new_bucket_name() def get_bucket_info_args(self) -> tuple[str, str]: - return '--bucket-info', json.dumps(self.api_wrapper.new_bucket_info(), ensure_ascii=True) + return '--bucket-info', json.dumps(self.bucket_manager.new_bucket_info(), ensure_ascii=True) def run_command(self, args, additional_env: dict | None = None): """ @@ -622,12 +361,12 @@ def cleanup_bucket(self, bucket_name: str, bucket_dict: dict | None = None) -> N except (ValueError, AssertionError): # bucket doesn't exist return - bucket = self.api_wrapper.api.BUCKET_CLASS( - api=self.api_wrapper.api, + bucket = self.bucket_manager.b2_api.BUCKET_CLASS( + api=self.bucket_manager.b2_api, id_=bucket_dict['bucketId'], name=bucket_name, ) - self.api_wrapper.clean_bucket(bucket) + self.bucket_manager.clean_bucket(bucket) class TempDir: diff --git a/test/integration/persistent_bucket.py b/test/integration/persistent_bucket.py index 220ffc9b7..c31327e42 100644 --- a/test/integration/persistent_bucket.py +++ b/test/integration/persistent_bucket.py @@ -11,13 +11,11 @@ import os from dataclasses import dataclass from functools import cached_property -from typing import List import tenacity from b2sdk.v3 import Bucket from b2sdk.v3.exception import DuplicateBucketName, NonExistentBucket - -from test.integration.helpers import BUCKET_NAME_LENGTH, Api +from b2sdk.v3.testing import BUCKET_NAME_LENGTH, BucketManager PERSISTENT_BUCKET_NAME_PREFIX = 'constst' @@ -32,8 +30,8 @@ def virtual_bucket_name(self): return f'{self.bucket_name}/{self.subfolder}' -def get_persistent_bucket_name(b2_api: Api) -> str: - bucket_base = os.environ.get('GITHUB_REPOSITORY_ID', b2_api.api.get_account_id()) +def get_persistent_bucket_name(bucket_manager: BucketManager) -> str: + bucket_base = os.environ.get('GITHUB_REPOSITORY_ID', bucket_manager.b2_api.get_account_id()) bucket_hash = hashlib.sha256(bucket_base.encode()).hexdigest() return f'{PERSISTENT_BUCKET_NAME_PREFIX}-{bucket_hash}'[:BUCKET_NAME_LENGTH] @@ -43,12 +41,12 @@ def get_persistent_bucket_name(b2_api: Api) -> str: wait=tenacity.wait_exponential_jitter(), stop=tenacity.stop_after_attempt(3), ) -def get_or_create_persistent_bucket(b2_api: Api) -> Bucket: - bucket_name = get_persistent_bucket_name(b2_api) +def get_or_create_persistent_bucket(bucket_manager: BucketManager) -> Bucket: + bucket_name = get_persistent_bucket_name(bucket_manager) try: - bucket = b2_api.api.get_bucket_by_name(bucket_name) + bucket = bucket_manager.b2_api.get_bucket_by_name(bucket_name) except NonExistentBucket: - bucket = b2_api.api.create_bucket( + bucket = bucket_manager.b2_api.create_bucket( bucket_name, bucket_type='allPublic', lifecycle_rules=[ @@ -60,11 +58,11 @@ def get_or_create_persistent_bucket(b2_api: Api) -> Bucket: ], ) # add the new bucket name to the list of bucket names - b2_api.bucket_name_log.append(bucket_name) + bucket_manager.bucket_name_log.append(bucket_name) return bucket -def prune_used_files(b2_api: Api, bucket: Bucket, folders: List[str]): - b2_api.clean_bucket( - bucket_object=bucket, only_files=True, only_folders=folders, ignore_retentions=True +def prune_used_files(bucket_manager: BucketManager, bucket: Bucket, folders: list[str]): + bucket_manager.clean_bucket( + bucket=bucket, only_files=True, only_folders=folders, ignore_retentions=True ) diff --git a/test/integration/test_b2_command_line.py b/test/integration/test_b2_command_line.py index 923be5646..14f2d9f42 100755 --- a/test/integration/test_b2_command_line.py +++ b/test/integration/test_b2_command_line.py @@ -38,6 +38,7 @@ fix_windows_path_limit, ) from b2sdk.v3.exception import MissingAccountData +from b2sdk.v3.testing import IntegrationTestBase from b2._internal._cli.const import ( B2_APPLICATION_KEY_ENV_VAR, @@ -668,170 +669,176 @@ def test_bucket(b2_tool, persistent_bucket): ] -def test_key_restrictions(b2_tool, bucket_name, sample_file, bucket_factory, b2_uri_args): - # A single file for rm to fail on. - b2_tool.should_succeed(['file', 'upload', '--no-progress', bucket_name, sample_file, 'test']) - - key_one_name = 'clt-testKey-01' + random_hex(6) - created_key_stdout = b2_tool.should_succeed( - [ - 'key', - 'create', - key_one_name, - 'listFiles,listBuckets,readFiles,writeKeys', - ] - ) - key_one_id, key_one = created_key_stdout.split() +class TestKeyRestrictions(IntegrationTestBase): + def test_key_restrictions(self, b2_tool, bucket_name, sample_file, b2_uri_args): + # A single file for rm to fail on. + b2_tool.should_succeed( + ['file', 'upload', '--no-progress', bucket_name, sample_file, 'test'] + ) - b2_tool.should_succeed( - ['account', 'authorize', '--environment', b2_tool.realm, key_one_id, key_one], - ) + key_one_name = 'clt-testKey-01' + random_hex(6) + created_key_stdout = b2_tool.should_succeed( + [ + 'key', + 'create', + key_one_name, + 'listFiles,listBuckets,readFiles,writeKeys', + ] + ) + key_one_id, key_one = created_key_stdout.split() - b2_tool.should_succeed( - ['bucket', 'get', bucket_name], - ) - second_bucket_name = bucket_factory().name - b2_tool.should_succeed( - ['bucket', 'get', second_bucket_name], - ) + b2_tool.should_succeed( + ['account', 'authorize', '--environment', b2_tool.realm, key_one_id, key_one], + ) - key_two_name = 'clt-testKey-02' + random_hex(6) - created_key_two_stdout = b2_tool.should_succeed( - [ - 'key', - 'create', - '--bucket', - bucket_name, - key_two_name, - 'listFiles,listBuckets,readFiles', - ] - ) - key_two_id, key_two = created_key_two_stdout.split() + b2_tool.should_succeed( + ['bucket', 'get', bucket_name], + ) + second_bucket_name = self.create_bucket().name + b2_tool.should_succeed( + ['bucket', 'get', second_bucket_name], + ) - create_key_deprecated_pattern = re.compile( - re.escape('WARNING: `create-key` command is deprecated. Use `key create` instead.') - ) - key_three_name = 'clt-testKey-03' + random_hex(6) - created_key_three_stdout = b2_tool.should_succeed( - [ - 'create-key', - '--bucket', - bucket_name, - key_three_name, - 'listFiles,listBuckets,readFiles', - ], - expected_stderr_pattern=create_key_deprecated_pattern, - ) - key_three_id, key_three = created_key_three_stdout.split() + key_two_name = 'clt-testKey-02' + random_hex(6) + created_key_two_stdout = b2_tool.should_succeed( + [ + 'key', + 'create', + '--bucket', + bucket_name, + key_two_name, + 'listFiles,listBuckets,readFiles', + ] + ) + key_two_id, key_two = created_key_two_stdout.split() - b2_tool.should_succeed( - ['account', 'authorize', '--environment', b2_tool.realm, key_two_id, key_two], - ) - b2_tool.should_succeed( - ['bucket', 'get', bucket_name], - ) - b2_tool.should_succeed( - ['ls', *b2_uri_args(bucket_name)], - ) + create_key_deprecated_pattern = re.compile( + re.escape('WARNING: `create-key` command is deprecated. Use `key create` instead.') + ) + key_three_name = 'clt-testKey-03' + random_hex(6) + created_key_three_stdout = b2_tool.should_succeed( + [ + 'create-key', + '--bucket', + bucket_name, + key_three_name, + 'listFiles,listBuckets,readFiles', + ], + expected_stderr_pattern=create_key_deprecated_pattern, + ) + key_three_id, key_three = created_key_three_stdout.split() - b2_tool.should_succeed( - ['account', 'authorize', '--environment', b2_tool.realm, key_three_id, key_three], - ) + b2_tool.should_succeed( + ['account', 'authorize', '--environment', b2_tool.realm, key_two_id, key_two], + ) + b2_tool.should_succeed( + ['bucket', 'get', bucket_name], + ) + b2_tool.should_succeed( + ['ls', *b2_uri_args(bucket_name)], + ) - # Capabilities can be listed in any order. While this regex doesn't confirm that all three are present, - # in ensures that there are three in total. - failed_bucket_err = ( - r'Deletion of file "test" \([^\)]+\) failed: unauthorized for ' - r'application key with capabilities ' - r"'(.*listFiles.*|.*listBuckets.*|.*readFiles.*){3}', " - r"restricted to buckets \['%s'\] \(unauthorized\)" % bucket_name - ) - b2_tool.should_fail( - ['rm', '--recursive', '--no-progress', *b2_uri_args(bucket_name)], failed_bucket_err - ) + b2_tool.should_succeed( + ['account', 'authorize', '--environment', b2_tool.realm, key_three_id, key_three], + ) - failed_bucket_err = rf"ERROR: Application key is restricted to buckets: \['{bucket_name}'\]" - b2_tool.should_fail(['bucket', 'get', second_bucket_name], failed_bucket_err) + # Capabilities can be listed in any order. While this regex doesn't confirm that all three are present, + # in ensures that there are three in total. + failed_bucket_err = ( + r'Deletion of file "test" \([^\)]+\) failed: unauthorized for ' + r'application key with capabilities ' + r"'(.*listFiles.*|.*listBuckets.*|.*readFiles.*){3}', " + r"restricted to buckets \['%s'\] \(unauthorized\)" % bucket_name + ) + b2_tool.should_fail( + ['rm', '--recursive', '--no-progress', *b2_uri_args(bucket_name)], failed_bucket_err + ) - failed_list_files_err = rf"ERROR: Application key is restricted to buckets: \['{bucket_name}'\]" - b2_tool.should_fail(['ls', *b2_uri_args(second_bucket_name)], failed_list_files_err) + failed_bucket_err = rf"ERROR: Application key is restricted to buckets: \['{bucket_name}'\]" + b2_tool.should_fail(['bucket', 'get', second_bucket_name], failed_bucket_err) - failed_list_files_err = rf"ERROR: Application key is restricted to buckets: \['{bucket_name}'\]" - b2_tool.should_fail(['rm', *b2_uri_args(second_bucket_name)], failed_list_files_err) + failed_list_files_err = ( + rf"ERROR: Application key is restricted to buckets: \['{bucket_name}'\]" + ) + b2_tool.should_fail(['ls', *b2_uri_args(second_bucket_name)], failed_list_files_err) - # reauthorize with more capabilities for clean up - b2_tool.should_succeed( - [ - 'account', - 'authorize', - '--environment', - b2_tool.realm, - b2_tool.account_id, - b2_tool.application_key, - ] - ) - b2_tool.should_succeed(['key', 'delete', key_one_id]) - b2_tool.should_succeed(['key', 'delete', key_two_id]) + failed_list_files_err = ( + rf"ERROR: Application key is restricted to buckets: \['{bucket_name}'\]" + ) + b2_tool.should_fail(['rm', *b2_uri_args(second_bucket_name)], failed_list_files_err) - delete_key_deprecated_pattern = re.compile( - re.escape('WARNING: `delete-key` command is deprecated. Use `key delete` instead.') - ) - b2_tool.should_succeed( - ['delete-key', key_three_id], - expected_stderr_pattern=delete_key_deprecated_pattern, - ) + # reauthorize with more capabilities for clean up + b2_tool.should_succeed( + [ + 'account', + 'authorize', + '--environment', + b2_tool.realm, + b2_tool.account_id, + b2_tool.application_key, + ] + ) + b2_tool.should_succeed(['key', 'delete', key_one_id]) + b2_tool.should_succeed(['key', 'delete', key_two_id]) + delete_key_deprecated_pattern = re.compile( + re.escape('WARNING: `delete-key` command is deprecated. Use `key delete` instead.') + ) + b2_tool.should_succeed( + ['delete-key', key_three_id], + expected_stderr_pattern=delete_key_deprecated_pattern, + ) -def test_multi_bucket_key_restrictions(b2_tool, bucket_factory): - bucket_a = bucket_factory() - bucket_b = bucket_factory() - bucket_c = bucket_factory() + def test_multi_bucket_key_restrictions(self, b2_tool): + bucket_a = self.create_bucket() + bucket_b = self.create_bucket() + bucket_c = self.create_bucket() - key_name = 'clt-testKey-01' + random_hex(6) + key_name = 'clt-testKey-01' + random_hex(6) - created_key_stdout = b2_tool.should_succeed( - [ - 'key', - 'create', - '--bucket', - bucket_a.name, - '--bucket', - bucket_b.name, - key_name, - 'listFiles,listBuckets,readFiles', - ] - ) + created_key_stdout = b2_tool.should_succeed( + [ + 'key', + 'create', + '--bucket', + bucket_a.name, + '--bucket', + bucket_b.name, + key_name, + 'listFiles,listBuckets,readFiles', + ] + ) - mb_key_id, mb_key = created_key_stdout.split() + mb_key_id, mb_key = created_key_stdout.split() - b2_tool.should_succeed( - ['account', 'authorize', '--environment', b2_tool.realm, mb_key_id, mb_key], - ) + b2_tool.should_succeed( + ['account', 'authorize', '--environment', b2_tool.realm, mb_key_id, mb_key], + ) - b2_tool.should_succeed( - ['bucket', 'get', bucket_a.name], - ) - b2_tool.should_succeed( - ['bucket', 'get', bucket_b.name], - ) + b2_tool.should_succeed( + ['bucket', 'get', bucket_a.name], + ) + b2_tool.should_succeed( + ['bucket', 'get', bucket_b.name], + ) - failed_bucket_err = rf"ERROR: Application key is restricted to buckets: \['{bucket_a.name}', '{bucket_b.name}'|'{bucket_b.name}', '{bucket_a.name}'\]" + failed_bucket_err = rf"ERROR: Application key is restricted to buckets: \['{bucket_a.name}', '{bucket_b.name}'|'{bucket_b.name}', '{bucket_a.name}'\]" - b2_tool.should_fail(['bucket', 'get', bucket_c.name], failed_bucket_err) + b2_tool.should_fail(['bucket', 'get', bucket_c.name], failed_bucket_err) - # reauthorize with more capabilities for clean up - b2_tool.should_succeed( - [ - 'account', - 'authorize', - '--environment', - b2_tool.realm, - b2_tool.account_id, - b2_tool.application_key, - ] - ) + # reauthorize with more capabilities for clean up + b2_tool.should_succeed( + [ + 'account', + 'authorize', + '--environment', + b2_tool.realm, + b2_tool.account_id, + b2_tool.application_key, + ] + ) - b2_tool.should_succeed(['key', 'delete', mb_key_id]) + b2_tool.should_succeed(['key', 'delete', mb_key_id]) def test_delete_bucket(b2_tool, bucket_name): @@ -1446,286 +1453,278 @@ def sync_down_helper(b2_tool, bucket_name, folder_in_bucket, sample_file, encryp ) -def test_sync_copy(bucket_factory, b2_tool, bucket_name, sample_file): - prepare_and_run_sync_copy_tests( - bucket_factory, b2_tool, bucket_name, 'sync', sample_file=sample_file - ) - +class TestSyncCopy(IntegrationTestBase): + def test_sync_copy(self, b2_tool, bucket_name, sample_file): + self.prepare_and_run_sync_copy_tests(b2_tool, bucket_name, 'sync', sample_file=sample_file) -def test_sync_copy_no_prefix_default_encryption(bucket_factory, b2_tool, bucket_name, sample_file): - prepare_and_run_sync_copy_tests( - bucket_factory, - b2_tool, - bucket_name, - '', - sample_file=sample_file, - destination_encryption=None, - expected_encryption=SSE_NONE, - ) + def test_sync_copy_no_prefix_default_encryption(self, b2_tool, bucket_name, sample_file): + self.prepare_and_run_sync_copy_tests( + b2_tool, + bucket_name, + '', + sample_file=sample_file, + destination_encryption=None, + expected_encryption=SSE_NONE, + ) + def test_sync_copy_no_prefix_no_encryption(self, b2_tool, bucket_name, sample_file): + self.prepare_and_run_sync_copy_tests( + b2_tool, + bucket_name, + '', + sample_file=sample_file, + destination_encryption=SSE_NONE, + expected_encryption=SSE_NONE, + ) -def test_sync_copy_no_prefix_no_encryption(bucket_factory, b2_tool, bucket_name, sample_file): - prepare_and_run_sync_copy_tests( - bucket_factory, - b2_tool, - bucket_name, - '', - sample_file=sample_file, - destination_encryption=SSE_NONE, - expected_encryption=SSE_NONE, - ) + def test_sync_copy_no_prefix_sse_b2(self, b2_tool, bucket_name, sample_file): + self.prepare_and_run_sync_copy_tests( + b2_tool, + bucket_name, + '', + sample_file=sample_file, + destination_encryption=SSE_B2_AES, + expected_encryption=SSE_B2_AES, + ) + def test_sync_copy_no_prefix_sse_c(self, b2_tool, bucket_name, sample_file): + self.prepare_and_run_sync_copy_tests( + b2_tool, + bucket_name, + '', + sample_file=sample_file, + destination_encryption=SSE_C_AES, + expected_encryption=SSE_C_AES, + source_encryption=SSE_C_AES_2, + ) -def test_sync_copy_no_prefix_sse_b2(bucket_factory, b2_tool, bucket_name, sample_file): - prepare_and_run_sync_copy_tests( - bucket_factory, - b2_tool, - bucket_name, - '', - sample_file=sample_file, - destination_encryption=SSE_B2_AES, - expected_encryption=SSE_B2_AES, - ) + def test_sync_copy_sse_c_single_bucket(self, b2_tool, bucket_name, sample_file): + self.run_sync_copy_with_basic_checks( + b2_tool=b2_tool, + b2_file_prefix='first_folder/', + b2_sync_point=f'b2:{bucket_name}/first_folder', + bucket_name=bucket_name, + other_b2_sync_point=f'b2:{bucket_name}/second_folder', + destination_encryption=SSE_C_AES_2, + source_encryption=SSE_C_AES, + sample_file=sample_file, + ) + expected_encryption_first = encryption_summary( + SSE_C_AES.as_dict(), + {SSE_C_KEY_ID_FILE_INFO_KEY_NAME: SSE_C_AES.key.key_id}, + ) + expected_encryption_second = encryption_summary( + SSE_C_AES_2.as_dict(), + {SSE_C_KEY_ID_FILE_INFO_KEY_NAME: SSE_C_AES_2.key.key_id}, + ) + file_versions = b2_tool.list_file_versions(bucket_name) + should_equal( + [ + ('+ first_folder/a', expected_encryption_first), + ('+ first_folder/b', expected_encryption_first), + ('+ second_folder/a', expected_encryption_second), + ('+ second_folder/b', expected_encryption_second), + ], + file_version_summary_with_encryption(file_versions), + ) -def test_sync_copy_no_prefix_sse_c(bucket_factory, b2_tool, bucket_name, sample_file): - prepare_and_run_sync_copy_tests( - bucket_factory, + def prepare_and_run_sync_copy_tests( + self, b2_tool, bucket_name, - '', - sample_file=sample_file, - destination_encryption=SSE_C_AES, - expected_encryption=SSE_C_AES, - source_encryption=SSE_C_AES_2, - ) - - -def test_sync_copy_sse_c_single_bucket(b2_tool, bucket_name, sample_file): - run_sync_copy_with_basic_checks( - b2_tool=b2_tool, - b2_file_prefix='first_folder/', - b2_sync_point=f'b2:{bucket_name}/first_folder', - bucket_name=bucket_name, - other_b2_sync_point=f'b2:{bucket_name}/second_folder', - destination_encryption=SSE_C_AES_2, - source_encryption=SSE_C_AES, - sample_file=sample_file, - ) - expected_encryption_first = encryption_summary( - SSE_C_AES.as_dict(), - {SSE_C_KEY_ID_FILE_INFO_KEY_NAME: SSE_C_AES.key.key_id}, - ) - expected_encryption_second = encryption_summary( - SSE_C_AES_2.as_dict(), - {SSE_C_KEY_ID_FILE_INFO_KEY_NAME: SSE_C_AES_2.key.key_id}, - ) - - file_versions = b2_tool.list_file_versions(bucket_name) - should_equal( - [ - ('+ first_folder/a', expected_encryption_first), - ('+ first_folder/b', expected_encryption_first), - ('+ second_folder/a', expected_encryption_second), - ('+ second_folder/b', expected_encryption_second), - ], - file_version_summary_with_encryption(file_versions), - ) - - -def prepare_and_run_sync_copy_tests( - bucket_factory, - b2_tool, - bucket_name, - folder_in_bucket, - sample_file, - destination_encryption=None, - expected_encryption=SSE_NONE, - source_encryption=None, -): - b2_sync_point = f'b2:{bucket_name}' - if folder_in_bucket: - b2_sync_point += '/' + folder_in_bucket - b2_file_prefix = folder_in_bucket + '/' - else: - b2_file_prefix = '' - - other_bucket_name = bucket_factory().name - - other_b2_sync_point = f'b2:{other_bucket_name}' - if folder_in_bucket: - other_b2_sync_point += '/' + folder_in_bucket - - run_sync_copy_with_basic_checks( - b2_tool=b2_tool, - b2_file_prefix=b2_file_prefix, - b2_sync_point=b2_sync_point, - bucket_name=bucket_name, - other_b2_sync_point=other_b2_sync_point, - destination_encryption=destination_encryption, - source_encryption=source_encryption, - sample_file=sample_file, - ) - - if destination_encryption is None or destination_encryption in (SSE_NONE, SSE_B2_AES): - encryption_file_info = {} - elif destination_encryption.mode == EncryptionMode.SSE_C: - encryption_file_info = {SSE_C_KEY_ID_FILE_INFO_KEY_NAME: destination_encryption.key.key_id} - else: - raise NotImplementedError(destination_encryption) - - file_versions = b2_tool.list_file_versions(other_bucket_name) - expected_encryption_str = encryption_summary( - expected_encryption.as_dict(), encryption_file_info - ) - should_equal( - [ - ('+ ' + b2_file_prefix + 'a', expected_encryption_str), - ('+ ' + b2_file_prefix + 'b', expected_encryption_str), - ], - file_version_summary_with_encryption(file_versions), - ) + folder_in_bucket, + sample_file, + destination_encryption=None, + expected_encryption=SSE_NONE, + source_encryption=None, + ): + b2_sync_point = f'b2:{bucket_name}' + if folder_in_bucket: + b2_sync_point += '/' + folder_in_bucket + b2_file_prefix = folder_in_bucket + '/' + else: + b2_file_prefix = '' + + other_bucket_name = self.create_bucket().name + + other_b2_sync_point = f'b2:{other_bucket_name}' + if folder_in_bucket: + other_b2_sync_point += '/' + folder_in_bucket + + self.run_sync_copy_with_basic_checks( + b2_tool=b2_tool, + b2_file_prefix=b2_file_prefix, + b2_sync_point=b2_sync_point, + bucket_name=bucket_name, + other_b2_sync_point=other_b2_sync_point, + destination_encryption=destination_encryption, + source_encryption=source_encryption, + sample_file=sample_file, + ) + if destination_encryption is None or destination_encryption in (SSE_NONE, SSE_B2_AES): + encryption_file_info = {} + elif destination_encryption.mode == EncryptionMode.SSE_C: + encryption_file_info = { + SSE_C_KEY_ID_FILE_INFO_KEY_NAME: destination_encryption.key.key_id + } + else: + raise NotImplementedError(destination_encryption) -def run_sync_copy_with_basic_checks( - b2_tool, - b2_file_prefix, - b2_sync_point, - bucket_name, - other_b2_sync_point, - destination_encryption, - source_encryption, - sample_file, -): - # Put a couple files in B2 - if source_encryption is None or source_encryption.mode in ( - EncryptionMode.NONE, - EncryptionMode.SSE_B2, - ): - b2_tool.should_succeed( - [ - 'file', - 'upload', - '--no-progress', - '--destination-server-side-encryption', - 'SSE-B2', - bucket_name, - sample_file, - b2_file_prefix + 'a', - ] + file_versions = b2_tool.list_file_versions(other_bucket_name) + expected_encryption_str = encryption_summary( + expected_encryption.as_dict(), encryption_file_info ) - b2_tool.should_succeed( - ['file', 'upload', '--no-progress', bucket_name, sample_file, b2_file_prefix + 'b'] + should_equal( + [ + ('+ ' + b2_file_prefix + 'a', expected_encryption_str), + ('+ ' + b2_file_prefix + 'b', expected_encryption_str), + ], + file_version_summary_with_encryption(file_versions), ) - elif source_encryption.mode == EncryptionMode.SSE_C: - for suffix in ['a', 'b']: + + def run_sync_copy_with_basic_checks( + self, + b2_tool, + b2_file_prefix, + b2_sync_point, + bucket_name, + other_b2_sync_point, + destination_encryption, + source_encryption, + sample_file, + ): + # Put a couple files in B2 + if source_encryption is None or source_encryption.mode in ( + EncryptionMode.NONE, + EncryptionMode.SSE_B2, + ): b2_tool.should_succeed( [ 'file', 'upload', '--no-progress', '--destination-server-side-encryption', - 'SSE-C', + 'SSE-B2', bucket_name, sample_file, - b2_file_prefix + suffix, + b2_file_prefix + 'a', + ] + ) + b2_tool.should_succeed( + ['file', 'upload', '--no-progress', bucket_name, sample_file, b2_file_prefix + 'b'] + ) + elif source_encryption.mode == EncryptionMode.SSE_C: + for suffix in ['a', 'b']: + b2_tool.should_succeed( + [ + 'file', + 'upload', + '--no-progress', + '--destination-server-side-encryption', + 'SSE-C', + bucket_name, + sample_file, + b2_file_prefix + suffix, + ], + additional_env={ + 'B2_DESTINATION_SSE_C_KEY_B64': base64.b64encode( + source_encryption.key.secret + ).decode(), + 'B2_DESTINATION_SSE_C_KEY_ID': source_encryption.key.key_id, + }, + ) + else: + raise NotImplementedError(source_encryption) + + # Sync all the files + if destination_encryption is None or destination_encryption == SSE_NONE: + b2_tool.should_succeed(['sync', '--no-progress', b2_sync_point, other_b2_sync_point]) + elif destination_encryption == SSE_B2_AES: + b2_tool.should_succeed( + [ + 'sync', + '--no-progress', + '--destination-server-side-encryption', + destination_encryption.mode.value, + b2_sync_point, + other_b2_sync_point, + ] + ) + elif destination_encryption.mode == EncryptionMode.SSE_C: + b2_tool.should_fail( + [ + 'sync', + '--no-progress', + '--destination-server-side-encryption', + destination_encryption.mode.value, + b2_sync_point, + other_b2_sync_point, ], additional_env={ 'B2_DESTINATION_SSE_C_KEY_B64': base64.b64encode( - source_encryption.key.secret + destination_encryption.key.secret ).decode(), - 'B2_DESTINATION_SSE_C_KEY_ID': source_encryption.key.key_id, + 'B2_DESTINATION_SSE_C_KEY_ID': destination_encryption.key.key_id, + }, + expected_pattern='b2sdk._internal.exception.BadRequest: The object was stored using a form of Server Side ' + 'Encryption. The correct parameters must be provided to retrieve the object. ' + r'\(bad_request\)', + ) + b2_tool.should_succeed( + [ + 'sync', + '--no-progress', + '--destination-server-side-encryption', + destination_encryption.mode.value, + '--source-server-side-encryption', + source_encryption.mode.value, + b2_sync_point, + other_b2_sync_point, + ], + additional_env={ + 'B2_DESTINATION_SSE_C_KEY_B64': base64.b64encode( + destination_encryption.key.secret + ).decode(), + 'B2_DESTINATION_SSE_C_KEY_ID': destination_encryption.key.key_id, + 'B2_SOURCE_SSE_C_KEY_B64': base64.b64encode( + source_encryption.key.secret + ).decode(), + 'B2_SOURCE_SSE_C_KEY_ID': source_encryption.key.key_id, }, ) - else: - raise NotImplementedError(source_encryption) - # Sync all the files - if destination_encryption is None or destination_encryption == SSE_NONE: - b2_tool.should_succeed(['sync', '--no-progress', b2_sync_point, other_b2_sync_point]) - elif destination_encryption == SSE_B2_AES: - b2_tool.should_succeed( - [ - 'sync', - '--no-progress', - '--destination-server-side-encryption', - destination_encryption.mode.value, - b2_sync_point, - other_b2_sync_point, - ] - ) - elif destination_encryption.mode == EncryptionMode.SSE_C: - b2_tool.should_fail( - [ - 'sync', - '--no-progress', - '--destination-server-side-encryption', - destination_encryption.mode.value, - b2_sync_point, - other_b2_sync_point, - ], - additional_env={ - 'B2_DESTINATION_SSE_C_KEY_B64': base64.b64encode( - destination_encryption.key.secret - ).decode(), - 'B2_DESTINATION_SSE_C_KEY_ID': destination_encryption.key.key_id, - }, - expected_pattern='b2sdk._internal.exception.BadRequest: The object was stored using a form of Server Side ' - 'Encryption. The correct parameters must be provided to retrieve the object. ' - r'\(bad_request\)', - ) - b2_tool.should_succeed( - [ - 'sync', - '--no-progress', - '--destination-server-side-encryption', - destination_encryption.mode.value, - '--source-server-side-encryption', - source_encryption.mode.value, - b2_sync_point, - other_b2_sync_point, - ], - additional_env={ - 'B2_DESTINATION_SSE_C_KEY_B64': base64.b64encode( - destination_encryption.key.secret - ).decode(), - 'B2_DESTINATION_SSE_C_KEY_ID': destination_encryption.key.key_id, - 'B2_SOURCE_SSE_C_KEY_B64': base64.b64encode(source_encryption.key.secret).decode(), - 'B2_SOURCE_SSE_C_KEY_ID': source_encryption.key.key_id, - }, + else: + raise NotImplementedError(destination_encryption) + + def test_sync_long_path(self, tmp_path, b2_tool, persistent_bucket): + """ + test sync with very long path (overcome windows 260 character limit) + """ + b2_sync_point = f'b2://{persistent_bucket.virtual_bucket_name}' + + long_path = '/'.join( + ( + 'extremely_long_path_which_exceeds_windows_unfortunate_260_character_path_limit', + 'and_needs_special_prefixes_containing_backslashes_added_to_overcome_this_limitation', + 'when_doing_so_beware_leaning_toothpick_syndrome_as_it_can_cause_frustration', + 'see_also_xkcd_1638', + ) ) - else: - raise NotImplementedError(destination_encryption) - + local_long_path = (tmp_path / long_path).resolve() + fixed_local_long_path = Path(fix_windows_path_limit(str(local_long_path))) + os.makedirs(fixed_local_long_path.parent) + write_file(fixed_local_long_path, b'asdf') -def test_sync_long_path(tmp_path, b2_tool, persistent_bucket): - """ - test sync with very long path (overcome windows 260 character limit) - """ - b2_sync_point = f'b2://{persistent_bucket.virtual_bucket_name}' - - long_path = '/'.join( - ( - 'extremely_long_path_which_exceeds_windows_unfortunate_260_character_path_limit', - 'and_needs_special_prefixes_containing_backslashes_added_to_overcome_this_limitation', - 'when_doing_so_beware_leaning_toothpick_syndrome_as_it_can_cause_frustration', - 'see_also_xkcd_1638', + b2_tool.should_succeed(['sync', '--no-progress', '--delete', str(tmp_path), b2_sync_point]) + file_versions = b2_tool.list_file_versions( + persistent_bucket.bucket_name, persistent_bucket.subfolder + ) + should_equal( + [f'+ {persistent_bucket.subfolder}/{long_path}'], file_version_summary(file_versions) ) - ) - - local_long_path = (tmp_path / long_path).resolve() - fixed_local_long_path = Path(fix_windows_path_limit(str(local_long_path))) - os.makedirs(fixed_local_long_path.parent) - write_file(fixed_local_long_path, b'asdf') - - b2_tool.should_succeed(['sync', '--no-progress', '--delete', str(tmp_path), b2_sync_point]) - file_versions = b2_tool.list_file_versions( - persistent_bucket.bucket_name, persistent_bucket.subfolder - ) - should_equal( - [f'+ {persistent_bucket.subfolder}/{long_path}'], file_version_summary(file_versions) - ) def test_default_sse_b2__update_bucket(b2_tool, bucket_name, schedule_bucket_cleanup): @@ -2287,368 +2286,384 @@ def test_license(b2_tool, with_packages, cli_version): SOFTWARE.""" in license_text.replace(os.linesep, '\n'), repr(license_text[-2000:]) -def test_file_lock( - b2_tool, - application_key_id, - application_key, - sample_file, - bucket_factory, - schedule_bucket_cleanup, -): - lock_disabled_bucket_name = bucket_factory(bucket_type='allPrivate').name +class TestFileLock(IntegrationTestBase): + def test_file_lock( + self, + b2_tool, + application_key_id, + application_key, + sample_file, + schedule_bucket_cleanup, + ): + lock_disabled_bucket_name = self.create_bucket().name - now_millis = current_time_millis() + now_millis = current_time_millis() - not_lockable_file = b2_tool.should_succeed_json( # file in a lock disabled bucket - ['file', 'upload', '--quiet', lock_disabled_bucket_name, sample_file, 'a'] - ) + not_lockable_file = b2_tool.should_succeed_json( # file in a lock disabled bucket + ['file', 'upload', '--quiet', lock_disabled_bucket_name, sample_file, 'a'] + ) - _assert_file_lock_configuration( - b2_tool, - not_lockable_file['fileId'], - retention_mode=RetentionMode.NONE, - legal_hold=LegalHold.UNSET, - ) + _assert_file_lock_configuration( + b2_tool, + not_lockable_file['fileId'], + retention_mode=RetentionMode.NONE, + legal_hold=LegalHold.UNSET, + ) - b2_tool.should_fail( - [ - 'file', - 'upload', - '--quiet', - lock_disabled_bucket_name, - sample_file, - 'a', - '--file-retention-mode', - 'governance', - '--retain-until', - str(now_millis + 1.5 * ONE_HOUR_MILLIS), - '--legal-hold', - 'on', - ], - r'ERROR: The bucket is not file lock enabled \(bucket_missing_file_lock\)', - ) + b2_tool.should_fail( + [ + 'file', + 'upload', + '--quiet', + lock_disabled_bucket_name, + sample_file, + 'a', + '--file-retention-mode', + 'governance', + '--retain-until', + str(now_millis + 1.5 * ONE_HOUR_MILLIS), + '--legal-hold', + 'on', + ], + r'ERROR: The bucket is not file lock enabled \(bucket_missing_file_lock\)', + ) - b2_tool.should_fail( - [ - 'bucket', - 'update', - lock_disabled_bucket_name, - 'allPrivate', - '--default-retention-mode', - 'compliance', - ], - 'ValueError: must specify period for retention mode RetentionMode.COMPLIANCE', - ) - b2_tool.should_fail( - [ - 'bucket', - 'update', - lock_disabled_bucket_name, - 'allPrivate', - '--default-retention-mode', - 'compliance', - '--default-retention-period', - '7 days', - ], - r'ERROR: The bucket is not file lock enabled \(bucket_missing_file_lock\)', - ) - lock_enabled_bucket_name = b2_tool.generate_bucket_name() - schedule_bucket_cleanup(lock_enabled_bucket_name) - b2_tool.should_succeed( - [ - 'bucket', - 'create', - lock_enabled_bucket_name, - 'allPrivate', - '--file-lock-enabled', - *b2_tool.get_bucket_info_args(), - ], - ) - updated_bucket = b2_tool.should_succeed_json( - [ - 'bucket', - 'update', - lock_enabled_bucket_name, - 'allPrivate', - '--default-retention-mode', - 'governance', - '--default-retention-period', - '1 days', - ], - ) - assert updated_bucket['defaultRetention'] == { - 'mode': 'governance', - 'period': { - 'duration': 1, - 'unit': 'days', - }, - } + b2_tool.should_fail( + [ + 'bucket', + 'update', + lock_disabled_bucket_name, + 'allPrivate', + '--default-retention-mode', + 'compliance', + ], + 'ValueError: must specify period for retention mode RetentionMode.COMPLIANCE', + ) + b2_tool.should_fail( + [ + 'bucket', + 'update', + lock_disabled_bucket_name, + 'allPrivate', + '--default-retention-mode', + 'compliance', + '--default-retention-period', + '7 days', + ], + r'ERROR: The bucket is not file lock enabled \(bucket_missing_file_lock\)', + ) + lock_enabled_bucket_name = b2_tool.generate_bucket_name() + schedule_bucket_cleanup(lock_enabled_bucket_name) + b2_tool.should_succeed( + [ + 'bucket', + 'create', + lock_enabled_bucket_name, + 'allPrivate', + '--file-lock-enabled', + *b2_tool.get_bucket_info_args(), + ], + ) + updated_bucket = b2_tool.should_succeed_json( + [ + 'bucket', + 'update', + lock_enabled_bucket_name, + 'allPrivate', + '--default-retention-mode', + 'governance', + '--default-retention-period', + '1 days', + ], + ) + assert updated_bucket['defaultRetention'] == { + 'mode': 'governance', + 'period': { + 'duration': 1, + 'unit': 'days', + }, + } - lockable_file = b2_tool.should_succeed_json( # file in a lock enabled bucket - ['file', 'upload', '--no-progress', '--quiet', lock_enabled_bucket_name, sample_file, 'a'] - ) + lockable_file = b2_tool.should_succeed_json( # file in a lock enabled bucket + [ + 'file', + 'upload', + '--no-progress', + '--quiet', + lock_enabled_bucket_name, + sample_file, + 'a', + ] + ) - # deprecated command - b2_tool.should_fail( - [ - 'update-file-retention', - not_lockable_file['fileName'], - not_lockable_file['fileId'], - 'governance', - '--retain-until', - str(now_millis + ONE_DAY_MILLIS + ONE_HOUR_MILLIS), - ], - r'ERROR: The bucket is not file lock enabled \(bucket_missing_file_lock\)', - ) + # deprecated command + b2_tool.should_fail( + [ + 'update-file-retention', + not_lockable_file['fileName'], + not_lockable_file['fileId'], + 'governance', + '--retain-until', + str(now_millis + ONE_DAY_MILLIS + ONE_HOUR_MILLIS), + ], + r'ERROR: The bucket is not file lock enabled \(bucket_missing_file_lock\)', + ) - # deprecated command - update_file_retention_deprecated_pattern = re.compile( - re.escape( - 'WARNING: `update-file-retention` command is deprecated. Use `file update` instead.' + # deprecated command + update_file_retention_deprecated_pattern = re.compile( + re.escape( + 'WARNING: `update-file-retention` command is deprecated. Use `file update` instead.' + ) + ) + b2_tool.should_succeed( # first let's try with a file name + [ + 'update-file-retention', + lockable_file['fileName'], + lockable_file['fileId'], + 'governance', + '--retain-until', + str(now_millis + ONE_DAY_MILLIS + ONE_HOUR_MILLIS), + ], + expected_stderr_pattern=update_file_retention_deprecated_pattern, ) - ) - b2_tool.should_succeed( # first let's try with a file name - [ - 'update-file-retention', - lockable_file['fileName'], - lockable_file['fileId'], - 'governance', - '--retain-until', - str(now_millis + ONE_DAY_MILLIS + ONE_HOUR_MILLIS), - ], - expected_stderr_pattern=update_file_retention_deprecated_pattern, - ) - lockable_b2uri = f"b2://{lock_enabled_bucket_name}/{lockable_file['fileName']}" - not_lockable_b2uri = f"b2://{lock_disabled_bucket_name}/{not_lockable_file['fileName']}" + lockable_b2uri = f"b2://{lock_enabled_bucket_name}/{lockable_file['fileName']}" + not_lockable_b2uri = f"b2://{lock_disabled_bucket_name}/{not_lockable_file['fileName']}" - _assert_file_lock_configuration( - b2_tool, - lockable_file['fileId'], - retention_mode=RetentionMode.GOVERNANCE, - retain_until=now_millis + ONE_DAY_MILLIS + ONE_HOUR_MILLIS, - ) + _assert_file_lock_configuration( + b2_tool, + lockable_file['fileId'], + retention_mode=RetentionMode.GOVERNANCE, + retain_until=now_millis + ONE_DAY_MILLIS + ONE_HOUR_MILLIS, + ) - b2_tool.should_succeed( # and now without a file name - [ - 'file', - 'update', - '--file-retention-mode', - 'governance', - '--retain-until', - str(now_millis + ONE_DAY_MILLIS + 2 * ONE_HOUR_MILLIS), - lockable_b2uri, - ], - ) + b2_tool.should_succeed( # and now without a file name + [ + 'file', + 'update', + '--file-retention-mode', + 'governance', + '--retain-until', + str(now_millis + ONE_DAY_MILLIS + 2 * ONE_HOUR_MILLIS), + lockable_b2uri, + ], + ) - _assert_file_lock_configuration( - b2_tool, - lockable_file['fileId'], - retention_mode=RetentionMode.GOVERNANCE, - retain_until=now_millis + ONE_DAY_MILLIS + 2 * ONE_HOUR_MILLIS, - ) + _assert_file_lock_configuration( + b2_tool, + lockable_file['fileId'], + retention_mode=RetentionMode.GOVERNANCE, + retain_until=now_millis + ONE_DAY_MILLIS + 2 * ONE_HOUR_MILLIS, + ) - b2_tool.should_fail( - [ - 'file', - 'update', - '--file-retention-mode', - 'governance', - '--retain-until', - str(now_millis + ONE_HOUR_MILLIS), - lockable_b2uri, - ], - "ERROR: Auth token not authorized to write retention or file already in 'compliance' mode or " - 'bypassGovernance=true parameter missing', - ) - b2_tool.should_succeed( - [ - 'file', - 'update', - '--file-retention-mode', - 'governance', - '--retain-until', - str(now_millis + ONE_HOUR_MILLIS), - '--bypass-governance', - lockable_b2uri, - ], - ) + b2_tool.should_fail( + [ + 'file', + 'update', + '--file-retention-mode', + 'governance', + '--retain-until', + str(now_millis + ONE_HOUR_MILLIS), + lockable_b2uri, + ], + "ERROR: Auth token not authorized to write retention or file already in 'compliance' mode or " + 'bypassGovernance=true parameter missing', + ) + b2_tool.should_succeed( + [ + 'file', + 'update', + '--file-retention-mode', + 'governance', + '--retain-until', + str(now_millis + ONE_HOUR_MILLIS), + '--bypass-governance', + lockable_b2uri, + ], + ) - _assert_file_lock_configuration( - b2_tool, - lockable_file['fileId'], - retention_mode=RetentionMode.GOVERNANCE, - retain_until=now_millis + ONE_HOUR_MILLIS, - ) + _assert_file_lock_configuration( + b2_tool, + lockable_file['fileId'], + retention_mode=RetentionMode.GOVERNANCE, + retain_until=now_millis + ONE_HOUR_MILLIS, + ) - b2_tool.should_fail( - ['file', 'update', '--file-retention-mode', 'none', lockable_b2uri], - "ERROR: Auth token not authorized to write retention or file already in 'compliance' mode or " - 'bypassGovernance=true parameter missing', - ) - b2_tool.should_succeed( - ['file', 'update', '--file-retention-mode', 'none', '--bypass-governance', lockable_b2uri], - ) + b2_tool.should_fail( + ['file', 'update', '--file-retention-mode', 'none', lockable_b2uri], + "ERROR: Auth token not authorized to write retention or file already in 'compliance' mode or " + 'bypassGovernance=true parameter missing', + ) + b2_tool.should_succeed( + [ + 'file', + 'update', + '--file-retention-mode', + 'none', + '--bypass-governance', + lockable_b2uri, + ], + ) - _assert_file_lock_configuration( - b2_tool, lockable_file['fileId'], retention_mode=RetentionMode.NONE - ) + _assert_file_lock_configuration( + b2_tool, lockable_file['fileId'], retention_mode=RetentionMode.NONE + ) - b2_tool.should_fail( - ['file', 'update', '--legal-hold', 'on', not_lockable_b2uri], - r'ERROR: The bucket is not file lock enabled \(bucket_missing_file_lock\)', - ) + b2_tool.should_fail( + ['file', 'update', '--legal-hold', 'on', not_lockable_b2uri], + r'ERROR: The bucket is not file lock enabled \(bucket_missing_file_lock\)', + ) - # deprecated command - update_file_legal_hold_deprecated_pattern = re.compile( - re.escape( - 'WARNING: `update-file-legal-hold` command is deprecated. Use `file update` instead.' + # deprecated command + update_file_legal_hold_deprecated_pattern = re.compile( + re.escape( + 'WARNING: `update-file-legal-hold` command is deprecated. Use `file update` instead.' + ) + ) + b2_tool.should_succeed( # first let's try with a file name + ['update-file-legal-hold', lockable_file['fileName'], lockable_file['fileId'], 'on'], + expected_stderr_pattern=update_file_legal_hold_deprecated_pattern, ) - ) - b2_tool.should_succeed( # first let's try with a file name - ['update-file-legal-hold', lockable_file['fileName'], lockable_file['fileId'], 'on'], - expected_stderr_pattern=update_file_legal_hold_deprecated_pattern, - ) - _assert_file_lock_configuration(b2_tool, lockable_file['fileId'], legal_hold=LegalHold.ON) + _assert_file_lock_configuration(b2_tool, lockable_file['fileId'], legal_hold=LegalHold.ON) - b2_tool.should_succeed( # and now without a file name - ['file', 'update', '--legal-hold', 'off', lockable_b2uri], - ) + b2_tool.should_succeed( # and now without a file name + ['file', 'update', '--legal-hold', 'off', lockable_b2uri], + ) - _assert_file_lock_configuration(b2_tool, lockable_file['fileId'], legal_hold=LegalHold.OFF) + _assert_file_lock_configuration(b2_tool, lockable_file['fileId'], legal_hold=LegalHold.OFF) - updated_bucket = b2_tool.should_succeed_json( - [ - 'bucket', - 'update', - lock_enabled_bucket_name, - 'allPrivate', - '--default-retention-mode', - 'none', - ], - ) - assert updated_bucket['defaultRetention'] == {'mode': None} + updated_bucket = b2_tool.should_succeed_json( + [ + 'bucket', + 'update', + lock_enabled_bucket_name, + 'allPrivate', + '--default-retention-mode', + 'none', + ], + ) + assert updated_bucket['defaultRetention'] == {'mode': None} - b2_tool.should_fail( - [ - 'file', - 'upload', - '--no-progress', - '--quiet', - lock_enabled_bucket_name, - sample_file, - 'a', - '--file-retention-mode', - 'governance', - '--retain-until', - str(now_millis - 1.5 * ONE_HOUR_MILLIS), - ], - r'ERROR: The retainUntilTimestamp must be in future \(retain_until_timestamp_must_be_in_future\)', - ) + b2_tool.should_fail( + [ + 'file', + 'upload', + '--no-progress', + '--quiet', + lock_enabled_bucket_name, + sample_file, + 'a', + '--file-retention-mode', + 'governance', + '--retain-until', + str(now_millis - 1.5 * ONE_HOUR_MILLIS), + ], + r'ERROR: The retainUntilTimestamp must be in future \(retain_until_timestamp_must_be_in_future\)', + ) - uploaded_file = b2_tool.should_succeed_json( - [ - 'file', - 'upload', - '--no-progress', - '--quiet', - lock_enabled_bucket_name, - sample_file, - 'a', - '--file-retention-mode', - 'governance', - '--retain-until', - str(now_millis + 1.5 * ONE_HOUR_MILLIS), - '--legal-hold', - 'on', - ] - ) + uploaded_file = b2_tool.should_succeed_json( + [ + 'file', + 'upload', + '--no-progress', + '--quiet', + lock_enabled_bucket_name, + sample_file, + 'a', + '--file-retention-mode', + 'governance', + '--retain-until', + str(now_millis + 1.5 * ONE_HOUR_MILLIS), + '--legal-hold', + 'on', + ] + ) - _assert_file_lock_configuration( - b2_tool, - uploaded_file['fileId'], - retention_mode=RetentionMode.GOVERNANCE, - retain_until=now_millis + 1.5 * ONE_HOUR_MILLIS, - legal_hold=LegalHold.ON, - ) + _assert_file_lock_configuration( + b2_tool, + uploaded_file['fileId'], + retention_mode=RetentionMode.GOVERNANCE, + retain_until=now_millis + 1.5 * ONE_HOUR_MILLIS, + legal_hold=LegalHold.ON, + ) - b2_tool.should_fail( - [ - 'file', - 'server-side-copy', - f'b2id://{lockable_file["fileId"]}', - f'b2://{lock_disabled_bucket_name}/copied', - '--file-retention-mode', - 'governance', - '--retain-until', - str(now_millis + 1.25 * ONE_HOUR_MILLIS), - '--legal-hold', - 'off', - ], - r'ERROR: The bucket is not file lock enabled \(bucket_missing_file_lock\)', - ) + b2_tool.should_fail( + [ + 'file', + 'server-side-copy', + f'b2id://{lockable_file["fileId"]}', + f'b2://{lock_disabled_bucket_name}/copied', + '--file-retention-mode', + 'governance', + '--retain-until', + str(now_millis + 1.25 * ONE_HOUR_MILLIS), + '--legal-hold', + 'off', + ], + r'ERROR: The bucket is not file lock enabled \(bucket_missing_file_lock\)', + ) - copied_file = b2_tool.should_succeed_json( - [ - 'file', - 'server-side-copy', - f"b2id://{lockable_file['fileId']}", - f'b2://{lock_enabled_bucket_name}/copied', - '--file-retention-mode', - 'governance', - '--retain-until', - str(now_millis + 1.25 * ONE_HOUR_MILLIS), - '--legal-hold', - 'off', - ] - ) + copied_file = b2_tool.should_succeed_json( + [ + 'file', + 'server-side-copy', + f"b2id://{lockable_file['fileId']}", + f'b2://{lock_enabled_bucket_name}/copied', + '--file-retention-mode', + 'governance', + '--retain-until', + str(now_millis + 1.25 * ONE_HOUR_MILLIS), + '--legal-hold', + 'off', + ] + ) - _assert_file_lock_configuration( - b2_tool, - copied_file['fileId'], - retention_mode=RetentionMode.GOVERNANCE, - retain_until=now_millis + 1.25 * ONE_HOUR_MILLIS, - legal_hold=LegalHold.OFF, - ) - lock_disabled_key_id, lock_disabled_key = make_lock_disabled_key(b2_tool) + _assert_file_lock_configuration( + b2_tool, + copied_file['fileId'], + retention_mode=RetentionMode.GOVERNANCE, + retain_until=now_millis + 1.25 * ONE_HOUR_MILLIS, + legal_hold=LegalHold.OFF, + ) + lock_disabled_key_id, lock_disabled_key = make_lock_disabled_key(b2_tool) - b2_tool.should_succeed( - [ - 'account', - 'authorize', - '--environment', - b2_tool.realm, - lock_disabled_key_id, - lock_disabled_key, - ], - ) + b2_tool.should_succeed( + [ + 'account', + 'authorize', + '--environment', + b2_tool.realm, + lock_disabled_key_id, + lock_disabled_key, + ], + ) - file_lock_without_perms_test( - b2_tool, - lock_enabled_bucket_name, - lock_disabled_bucket_name, - lockable_file['fileId'], - not_lockable_file['fileId'], - lockable_b2uri, - not_lockable_b2uri, - sample_file=sample_file, - ) + file_lock_without_perms_test( + b2_tool, + lock_enabled_bucket_name, + lock_disabled_bucket_name, + lockable_file['fileId'], + not_lockable_file['fileId'], + lockable_b2uri, + not_lockable_b2uri, + sample_file=sample_file, + ) - b2_tool.should_succeed( - [ - 'account', - 'authorize', - '--environment', - b2_tool.realm, - application_key_id, - application_key, - ], - ) + b2_tool.should_succeed( + [ + 'account', + 'authorize', + '--environment', + b2_tool.realm, + application_key_id, + application_key, + ], + ) - deleting_locked_files( - b2_tool, lock_enabled_bucket_name, lock_disabled_key_id, lock_disabled_key, sample_file - ) + deleting_locked_files( + b2_tool, lock_enabled_bucket_name, lock_disabled_key_id, lock_disabled_key, sample_file + ) def make_lock_disabled_key(b2_tool): diff --git a/test/unit/_cli/unpickle.py b/test/unit/_cli/unpickle.py index 898b59fa8..49f59314c 100644 --- a/test/unit/_cli/unpickle.py +++ b/test/unit/_cli/unpickle.py @@ -11,14 +11,14 @@ import io import pickle import sys -from typing import Any, Set +from typing import Any class Unpickler(pickle.Unpickler): """This Unpickler will raise an exception if loading the pickled object imports any b2sdk module.""" - _modules_to_load: Set[str] + _modules_to_load: set[str] def load(self): self._modules_to_load = set() diff --git a/test/unit/helpers.py b/test/unit/helpers.py index b7f1b7893..f4cb62696 100644 --- a/test/unit/helpers.py +++ b/test/unit/helpers.py @@ -8,7 +8,6 @@ # ###################################################################### import concurrent.futures -import sys class RunOrDieExecutor(concurrent.futures.ThreadPoolExecutor): @@ -21,20 +20,3 @@ class RunOrDieExecutor(concurrent.futures.ThreadPoolExecutor): def __exit__(self, exc_type, exc_val, exc_tb): self.shutdown(wait=False, cancel_futures=True) return super().__exit__(exc_type, exc_val, exc_tb) - - if sys.version_info < (3, 9): # shutdown(cancel_futures=True) is Python 3.9+ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._futures = [] - - def shutdown(self, wait=True, cancel_futures=False): - if cancel_futures: - for future in self._futures: - future.cancel() - super().shutdown(wait=wait) - - def submit(self, *args, **kwargs): - future = super().submit(*args, **kwargs) - self._futures.append(future) - return future diff --git a/test/unit/test_base.py b/test/unit/test_base.py index 1df7f9058..286bccd6e 100644 --- a/test/unit/test_base.py +++ b/test/unit/test_base.py @@ -11,14 +11,13 @@ import re import unittest from contextlib import contextmanager -from typing import Type import pytest @pytest.mark.usefixtures('unit_test_console_tool_class', 'b2_uri_args') class TestBase(unittest.TestCase): - console_tool_class: Type + console_tool_class: type @contextmanager def assertRaises(self, exc, msg=None): diff --git a/test/unit/test_console_tool.py b/test/unit/test_console_tool.py index 146e33b81..e113fe002 100644 --- a/test/unit/test_console_tool.py +++ b/test/unit/test_console_tool.py @@ -12,11 +12,11 @@ import os import pathlib import re -from functools import lru_cache +from functools import cache from io import StringIO from itertools import chain, product from tempfile import TemporaryDirectory -from typing import List, Optional +from typing import Optional from unittest import mock import pytest @@ -64,7 +64,7 @@ def setUp(self): self.raw_simulator = RawSimulator() self.api_config = B2HttpApiConfig(_raw_api_class=lambda *args, **kwargs: self.raw_simulator) - @lru_cache(maxsize=None) + @cache def _get_b2api(**kwargs) -> B2Api: kwargs.pop('profile', None) return B2Api( @@ -3586,7 +3586,7 @@ def test_rm_progress(self): def _run_problematic_removal( self, - additional_parameters: Optional[List[str]] = None, + additional_parameters: Optional[list[str]] = None, expected_in_stdout: Optional[str] = None, unexpected_in_stdout: Optional[str] = None, ): @@ -3677,7 +3677,7 @@ def test_rm_b2id(self): """ self._run_command(['ls', '--recursive', 'b2://my-bucket'], expected_stdout) - def rm_filters_helper(self, rm_args: List[str], expected_ls_stdout: str): + def rm_filters_helper(self, rm_args: list[str], expected_ls_stdout: str): self._authorize_account() self._run_command(['bucket', 'create', 'my-rm-bucket', 'allPublic'], 'bucket_1\n', '', 0) bucket = self.b2_api.get_bucket_by_name('my-rm-bucket')