From 566b67140dceda3d79b599e9d626c94a302aceaf Mon Sep 17 00:00:00 2001 From: Kacper Wolkiewicz Date: Mon, 28 Jul 2025 19:56:42 +0200 Subject: [PATCH 1/4] Move test helpers and fixtures to the project's testing module --- .gitignore | 3 +- b2sdk/_internal/testing/__init__.py | 0 .../_internal/testing}/cleanup_buckets.py | 8 +- b2sdk/_internal/testing/fixtures/__init__.py | 0 b2sdk/_internal/testing/fixtures/api.py | 44 ++++++++ b2sdk/_internal/testing/fixtures/buckets.py | 71 ++++++++++++ b2sdk/_internal/testing/helpers/__init__.py | 0 b2sdk/_internal/testing/helpers/api.py | 37 ++++++ .../_internal/testing/helpers}/base.py | 6 +- .../testing/helpers}/bucket_cleaner.py | 2 +- .../_internal/testing/helpers/buckets.py | 14 +-- test/integration/conftest.py | 105 +++--------------- test/integration/test_download.py | 5 +- .../test_file_version_attributes.py | 2 +- test/integration/test_raw_api.py | 4 +- test/integration/test_upload.py | 2 +- 16 files changed, 182 insertions(+), 121 deletions(-) create mode 100644 b2sdk/_internal/testing/__init__.py rename {test/integration => b2sdk/_internal/testing}/cleanup_buckets.py (71%) create mode 100644 b2sdk/_internal/testing/fixtures/__init__.py create mode 100644 b2sdk/_internal/testing/fixtures/api.py create mode 100644 b2sdk/_internal/testing/fixtures/buckets.py create mode 100644 b2sdk/_internal/testing/helpers/__init__.py create mode 100644 b2sdk/_internal/testing/helpers/api.py rename {test/integration => b2sdk/_internal/testing/helpers}/base.py (94%) rename {test/integration => b2sdk/_internal/testing/helpers}/bucket_cleaner.py (97%) rename test/integration/helpers.py => b2sdk/_internal/testing/helpers/buckets.py (72%) diff --git a/.gitignore b/.gitignore index 42d422259..7ee7c81ae 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ venv .venv .vscode .pdm-build/ -.pdm-python \ No newline at end of file +.pdm-python +.env \ No newline at end of file diff --git a/b2sdk/_internal/testing/__init__.py b/b2sdk/_internal/testing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/cleanup_buckets.py b/b2sdk/_internal/testing/cleanup_buckets.py similarity index 71% rename from test/integration/cleanup_buckets.py rename to b2sdk/_internal/testing/cleanup_buckets.py index 520f02199..a5970919d 100755 --- a/test/integration/cleanup_buckets.py +++ b/b2sdk/_internal/testing/cleanup_buckets.py @@ -11,12 +11,12 @@ from test.integration.helpers import authorize -from . import get_b2_auth_data -from .bucket_cleaner import BucketCleaner -from .test_raw_api import cleanup_old_buckets +from b2sdk._internal.testing.helpers.api import get_b2_auth_data +from b2sdk._internal.testing.helpers.bucket_cleaner import BucketCleaner +# from .test_raw_api import cleanup_old_buckets if __name__ == '__main__': - cleanup_old_buckets() + # cleanup_old_buckets() BucketCleaner( dont_cleanup_old_buckets=False, b2_api=authorize(get_b2_auth_data())[0] ).cleanup_buckets() diff --git a/b2sdk/_internal/testing/fixtures/__init__.py b/b2sdk/_internal/testing/fixtures/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/b2sdk/_internal/testing/fixtures/api.py b/b2sdk/_internal/testing/fixtures/api.py new file mode 100644 index 000000000..1b6b2945b --- /dev/null +++ b/b2sdk/_internal/testing/fixtures/api.py @@ -0,0 +1,44 @@ + +###################################################################### +# +# File: test/integration/conftest.py +# +# Copyright 2021 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### +from __future__ import annotations + +import http +import http.client +import os + +import pytest + +from b2sdk._internal.testing.helpers.api import get_b2_auth_data, authorize + + +@pytest.fixture(autouse=True, scope='session') +def set_http_debug(): + if os.environ.get('B2_DEBUG_HTTP'): + http.client.HTTPConnection.debuglevel = 1 + + +@pytest.fixture(scope='session') +def b2_auth_data(): + try: + return get_b2_auth_data() + except ValueError as ex: + pytest.fail(ex.args[0]) + + +@pytest.fixture(scope='session') +def _b2_api(b2_auth_data): + b2_api, _ = authorize(b2_auth_data) + return b2_api + + +@pytest.fixture(scope='session') +def b2_api(_b2_api, bucket_cleaner): + return _b2_api diff --git a/b2sdk/_internal/testing/fixtures/buckets.py b/b2sdk/_internal/testing/fixtures/buckets.py new file mode 100644 index 000000000..b440a81cd --- /dev/null +++ b/b2sdk/_internal/testing/fixtures/buckets.py @@ -0,0 +1,71 @@ +###################################################################### +# +# File: test/integration/conftest.py +# +# Copyright 2021 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### +from __future__ import annotations +import secrets + +import pytest + +from b2sdk._internal.utils import current_time_millis +from b2sdk._internal.testing.helpers.bucket_cleaner import BucketCleaner +from b2sdk._internal.testing.helpers.buckets import ( + BUCKET_CREATED_AT_MILLIS, + get_bucket_name_prefix, + random_bucket_name, +) + + +def pytest_addoption(parser): + """Add a flag for not cleaning up old buckets""" + parser.addoption( + '--dont-cleanup-old-buckets', + action='store_true', + default=False, + ) + + +@pytest.fixture(scope='session') +def dont_cleanup_old_buckets(request): + return request.config.getoption('--dont-cleanup-old-buckets') + + +@pytest.fixture(scope='session') +def bucket_name_prefix(): + return get_bucket_name_prefix(8) + + +@pytest.fixture(scope='session') +def bucket_cleaner(bucket_name_prefix, dont_cleanup_old_buckets, _b2_api): + cleaner = BucketCleaner( + dont_cleanup_old_buckets, + _b2_api, + current_run_prefix=bucket_name_prefix, + ) + yield cleaner + cleaner.cleanup_buckets() + + +@pytest.fixture +def bucket(b2_api, bucket_name_prefix, bucket_cleaner): + bucket = b2_api.create_bucket( + random_bucket_name(bucket_name_prefix), + 'allPrivate', + bucket_info={ + 'created_by': 'b2-sdk integration test', + BUCKET_CREATED_AT_MILLIS: str(current_time_millis()), + }, + ) + yield bucket + bucket_cleaner.cleanup_bucket(bucket) + + +@pytest.fixture +def b2_subfolder(bucket, request): + subfolder_name = f'{request.node.name}_{secrets.token_urlsafe(4)}' + return f'b2://{bucket.name}/{subfolder_name}' diff --git a/b2sdk/_internal/testing/helpers/__init__.py b/b2sdk/_internal/testing/helpers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/b2sdk/_internal/testing/helpers/api.py b/b2sdk/_internal/testing/helpers/api.py new file mode 100644 index 000000000..a88c68edd --- /dev/null +++ b/b2sdk/_internal/testing/helpers/api.py @@ -0,0 +1,37 @@ +###################################################################### +# +# File: test/integration/helpers.py +# +# Copyright 2022 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### +from __future__ import annotations + +import os + +from b2sdk.v3 import ( + DEFAULT_HTTP_API_CONFIG, + B2Api, + InMemoryAccountInfo, +) + + +def get_b2_auth_data(): + application_key_id = os.environ.get('B2_TEST_APPLICATION_KEY_ID') + if application_key_id is None: + raise ValueError('B2_TEST_APPLICATION_KEY_ID is not set.') + + application_key = os.environ.get('B2_TEST_APPLICATION_KEY') + if application_key is None: + raise ValueError('B2_TEST_APPLICATION_KEY is not set.') + return application_key_id, application_key + + +def authorize(b2_auth_data, api_config=DEFAULT_HTTP_API_CONFIG): + info = InMemoryAccountInfo() + b2_api = B2Api(info, api_config=api_config) + realm = os.environ.get('B2_TEST_ENVIRONMENT', 'production') + b2_api.authorize_account(*b2_auth_data, realm=realm) + return b2_api, info \ No newline at end of file diff --git a/test/integration/base.py b/b2sdk/_internal/testing/helpers/base.py similarity index 94% rename from test/integration/base.py rename to b2sdk/_internal/testing/helpers/base.py index a67baf969..37fe39310 100644 --- a/test/integration/base.py +++ b/b2sdk/_internal/testing/helpers/base.py @@ -13,8 +13,8 @@ from b2sdk.v2 import B2Api, current_time_millis from b2sdk.v2.exception import DuplicateBucketName -from test.integration.bucket_cleaner import BucketCleaner -from test.integration.helpers import ( +from b2sdk._internal.testing.helpers.bucket_cleaner import BucketCleaner +from b2sdk._internal.testing.helpers.buckets import ( BUCKET_CREATED_AT_MILLIS, random_bucket_name, ) @@ -58,7 +58,7 @@ def create_bucket(self): try: bucket = self.b2_api.create_bucket( bucket_name, - 'allPublic', + 'allPrivate', bucket_info={BUCKET_CREATED_AT_MILLIS: str(current_time_millis())}, ) except DuplicateBucketName: diff --git a/test/integration/bucket_cleaner.py b/b2sdk/_internal/testing/helpers/bucket_cleaner.py similarity index 97% rename from test/integration/bucket_cleaner.py rename to b2sdk/_internal/testing/helpers/bucket_cleaner.py index 05ffc8863..29ca42126 100644 --- a/test/integration/bucket_cleaner.py +++ b/b2sdk/_internal/testing/helpers/bucket_cleaner.py @@ -20,8 +20,8 @@ current_time_millis, ) from b2sdk.v3.exception import BadRequest +from b2sdk._internal.testing.helpers.buckets import BUCKET_CREATED_AT_MILLIS, GENERAL_BUCKET_NAME_PREFIX -from .helpers import BUCKET_CREATED_AT_MILLIS, GENERAL_BUCKET_NAME_PREFIX ONE_HOUR_MILLIS = 60 * 60 * 1000 diff --git a/test/integration/helpers.py b/b2sdk/_internal/testing/helpers/buckets.py similarity index 72% rename from test/integration/helpers.py rename to b2sdk/_internal/testing/helpers/buckets.py index bf44eacbe..0cd7da5e8 100644 --- a/test/integration/helpers.py +++ b/b2sdk/_internal/testing/helpers/buckets.py @@ -9,15 +9,11 @@ ###################################################################### from __future__ import annotations -import os import secrets from b2sdk.v3 import ( BUCKET_NAME_CHARS_UNIQ, BUCKET_NAME_LENGTH_RANGE, - DEFAULT_HTTP_API_CONFIG, - B2Api, - InMemoryAccountInfo, ) GENERAL_BUCKET_NAME_PREFIX = 'sdktst' @@ -36,12 +32,4 @@ def get_bucket_name_prefix(rnd_len: int = 8) -> str: def random_bucket_name(prefix: str = GENERAL_BUCKET_NAME_PREFIX) -> str: - return prefix + _bucket_name_prefix_part(BUCKET_NAME_LENGTH - len(prefix)) - - -def authorize(b2_auth_data, api_config=DEFAULT_HTTP_API_CONFIG): - info = InMemoryAccountInfo() - b2_api = B2Api(info, api_config=api_config) - realm = os.environ.get('B2_TEST_ENVIRONMENT', 'production') - b2_api.authorize_account(*b2_auth_data, realm=realm) - return b2_api, info + return prefix + _bucket_name_prefix_part(BUCKET_NAME_LENGTH - len(prefix)) \ No newline at end of file diff --git a/test/integration/conftest.py b/test/integration/conftest.py index 9e84a3fdd..3e8de637f 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -7,96 +7,17 @@ # License https://www.backblaze.com/using_b2_code.html # ###################################################################### -from __future__ import annotations - -import http -import http.client -import os -import secrets - -import pytest - -from b2sdk._internal.utils import current_time_millis -from test.integration import get_b2_auth_data -from test.integration.bucket_cleaner import BucketCleaner -from test.integration.helpers import ( - BUCKET_CREATED_AT_MILLIS, - authorize, - get_bucket_name_prefix, - random_bucket_name, +from b2sdk._internal.testing.fixtures.api import ( # noqa: F401 + set_http_debug, + b2_auth_data, + _b2_api, + b2_api, ) - - -def pytest_addoption(parser): - """Add a flag for not cleaning up old buckets""" - parser.addoption( - '--dont-cleanup-old-buckets', - action='store_true', - default=False, - ) - - -@pytest.fixture(scope='session') -def dont_cleanup_old_buckets(request): - return request.config.getoption('--dont-cleanup-old-buckets') - - -@pytest.fixture(autouse=True, scope='session') -def set_http_debug(): - if os.environ.get('B2_DEBUG_HTTP'): - http.client.HTTPConnection.debuglevel = 1 - - -@pytest.fixture(scope='session') -def b2_auth_data(): - try: - return get_b2_auth_data() - except ValueError as ex: - pytest.fail(ex.args[0]) - - -@pytest.fixture(scope='session') -def bucket_name_prefix(): - return get_bucket_name_prefix(8) - - -@pytest.fixture(scope='session') -def _b2_api(b2_auth_data): - b2_api, _ = authorize(b2_auth_data) - return b2_api - - -@pytest.fixture(scope='session') -def bucket_cleaner(bucket_name_prefix, dont_cleanup_old_buckets, _b2_api): - cleaner = BucketCleaner( - dont_cleanup_old_buckets, - _b2_api, - current_run_prefix=bucket_name_prefix, - ) - yield cleaner - cleaner.cleanup_buckets() - - -@pytest.fixture(scope='session') -def b2_api(_b2_api, bucket_cleaner): - return _b2_api - - -@pytest.fixture -def bucket(b2_api, bucket_name_prefix, bucket_cleaner): - bucket = b2_api.create_bucket( - random_bucket_name(bucket_name_prefix), - 'allPrivate', - bucket_info={ - 'created_by': 'b2-sdk integration test', - BUCKET_CREATED_AT_MILLIS: str(current_time_millis()), - }, - ) - yield bucket - bucket_cleaner.cleanup_bucket(bucket) - - -@pytest.fixture -def b2_subfolder(bucket, request): - subfolder_name = f'{request.node.name}_{secrets.token_urlsafe(4)}' - return f'b2://{bucket.name}/{subfolder_name}' +from b2sdk._internal.testing.fixtures.buckets import ( # noqa: F401 + pytest_addoption, + dont_cleanup_old_buckets, + bucket_name_prefix, + bucket_cleaner, + bucket, + b2_subfolder, +) \ No newline at end of file diff --git a/test/integration/test_download.py b/test/integration/test_download.py index 9b9ab691b..6c691b4ec 100644 --- a/test/integration/test_download.py +++ b/test/integration/test_download.py @@ -23,9 +23,8 @@ from b2sdk._internal.utils import Sha1HexDigest from b2sdk._internal.utils.filesystem import _IS_WINDOWS from b2sdk.v3 import * - -from .base import IntegrationTestBase -from .helpers import authorize +from b2sdk._internal.testing.helpers.base import IntegrationTestBase +from b2sdk._internal.testing.helpers.api import authorize class TestDownload(IntegrationTestBase): diff --git a/test/integration/test_file_version_attributes.py b/test/integration/test_file_version_attributes.py index f128b05c8..ed6ffa4a6 100644 --- a/test/integration/test_file_version_attributes.py +++ b/test/integration/test_file_version_attributes.py @@ -11,7 +11,7 @@ import datetime as dt -from .base import IntegrationTestBase +from b2sdk._internal.testing.helpers.base import IntegrationTestBase class TestFileVersionAttributes(IntegrationTestBase): diff --git a/test/integration/test_raw_api.py b/test/integration/test_raw_api.py index c60ec55b2..6306b895f 100644 --- a/test/integration/test_raw_api.py +++ b/test/integration/test_raw_api.py @@ -168,7 +168,7 @@ def raw_api_test_helper(raw_api, should_cleanup_old_buckets): account_auth_token, account_id, bucket_name, - 'allPublic', + 'allPrivate', is_file_lock_enabled=True, ) bucket_id = bucket_dict['bucketId'] @@ -208,7 +208,7 @@ def raw_api_test_helper(raw_api, should_cleanup_old_buckets): account_auth_token, account_id, replication_source_bucket_name, - 'allPublic', + 'allPrivate', is_file_lock_enabled=True, replication=ReplicationConfiguration( rules=[ diff --git a/test/integration/test_upload.py b/test/integration/test_upload.py index 1341b1293..a47ce4fd6 100644 --- a/test/integration/test_upload.py +++ b/test/integration/test_upload.py @@ -16,7 +16,7 @@ from b2sdk._internal.encryption.types import EncryptionAlgorithm, EncryptionMode from b2sdk.v2 import B2RawHTTPApi -from .base import IntegrationTestBase +from b2sdk._internal.testing.helpers.base import IntegrationTestBase from .test_raw_api import authorize_raw_api From f061a37472f08bbe48fb942a514fe64455417f92 Mon Sep 17 00:00:00 2001 From: Kacper Wolkiewicz Date: Sat, 2 Aug 2025 12:05:28 +0200 Subject: [PATCH 2/4] Move test helpers and fixtures to the library - working version --- b2sdk/_internal/testing/cleanup_buckets.py | 6 +- b2sdk/_internal/testing/fixtures/api.py | 2 +- b2sdk/_internal/testing/fixtures/buckets.py | 19 +- b2sdk/_internal/testing/helpers/api.py | 1 + b2sdk/_internal/testing/helpers/base.py | 10 +- .../testing/helpers/bucket_cleaner.py | 118 ------- .../testing/helpers/bucket_manager.py | 223 ++++++++++++ b2sdk/_internal/testing/helpers/buckets.py | 8 +- b2sdk/v0/testing/__init__.py | 12 + b2sdk/v1/testing/__init__.py | 12 + b2sdk/v2/testing/__init__.py | 12 + b2sdk/v3/testing/__init__.py | 48 +++ pdm.lock | 329 ++++++++++-------- pyproject.toml | 1 + test/integration/__init__.py | 12 - test/integration/conftest.py | 16 +- test/integration/test_download.py | 3 +- .../test_file_version_attributes.py | 2 +- test/integration/test_upload.py | 2 +- 19 files changed, 537 insertions(+), 299 deletions(-) delete mode 100644 b2sdk/_internal/testing/helpers/bucket_cleaner.py create mode 100644 b2sdk/_internal/testing/helpers/bucket_manager.py create mode 100644 b2sdk/v0/testing/__init__.py create mode 100644 b2sdk/v1/testing/__init__.py create mode 100644 b2sdk/v2/testing/__init__.py create mode 100644 b2sdk/v3/testing/__init__.py diff --git a/b2sdk/_internal/testing/cleanup_buckets.py b/b2sdk/_internal/testing/cleanup_buckets.py index a5970919d..7bc3fb904 100755 --- a/b2sdk/_internal/testing/cleanup_buckets.py +++ b/b2sdk/_internal/testing/cleanup_buckets.py @@ -12,11 +12,11 @@ from test.integration.helpers import authorize from b2sdk._internal.testing.helpers.api import get_b2_auth_data -from b2sdk._internal.testing.helpers.bucket_cleaner import BucketCleaner +from b2sdk._internal.testing.helpers.bucket_manager import BucketManager # from .test_raw_api import cleanup_old_buckets if __name__ == '__main__': # cleanup_old_buckets() - BucketCleaner( + BucketManager( dont_cleanup_old_buckets=False, b2_api=authorize(get_b2_auth_data())[0] - ).cleanup_buckets() + ).clean_buckets() diff --git a/b2sdk/_internal/testing/fixtures/api.py b/b2sdk/_internal/testing/fixtures/api.py index 1b6b2945b..b5a11749f 100644 --- a/b2sdk/_internal/testing/fixtures/api.py +++ b/b2sdk/_internal/testing/fixtures/api.py @@ -40,5 +40,5 @@ def _b2_api(b2_auth_data): @pytest.fixture(scope='session') -def b2_api(_b2_api, bucket_cleaner): +def b2_api(_b2_api, bucket_manager): return _b2_api diff --git a/b2sdk/_internal/testing/fixtures/buckets.py b/b2sdk/_internal/testing/fixtures/buckets.py index b440a81cd..993200529 100644 --- a/b2sdk/_internal/testing/fixtures/buckets.py +++ b/b2sdk/_internal/testing/fixtures/buckets.py @@ -13,11 +13,12 @@ import pytest from b2sdk._internal.utils import current_time_millis -from b2sdk._internal.testing.helpers.bucket_cleaner import BucketCleaner +from b2sdk._internal.testing.helpers.bucket_manager import BucketManager from b2sdk._internal.testing.helpers.buckets import ( BUCKET_CREATED_AT_MILLIS, get_bucket_name_prefix, random_bucket_name, + GENERAL_BUCKET_NAME_PREFIX, ) @@ -41,18 +42,24 @@ def bucket_name_prefix(): @pytest.fixture(scope='session') -def bucket_cleaner(bucket_name_prefix, dont_cleanup_old_buckets, _b2_api): - cleaner = BucketCleaner( +def general_bucket_name_prefix(): + return GENERAL_BUCKET_NAME_PREFIX + + +@pytest.fixture(scope='session') +def bucket_manager(bucket_name_prefix, general_bucket_name_prefix, dont_cleanup_old_buckets, _b2_api): + cleaner = BucketManager( dont_cleanup_old_buckets, _b2_api, current_run_prefix=bucket_name_prefix, + general_prefix=general_bucket_name_prefix ) yield cleaner - cleaner.cleanup_buckets() + cleaner.clean_buckets() @pytest.fixture -def bucket(b2_api, bucket_name_prefix, bucket_cleaner): +def bucket(b2_api, bucket_name_prefix, bucket_manager): bucket = b2_api.create_bucket( random_bucket_name(bucket_name_prefix), 'allPrivate', @@ -62,7 +69,7 @@ def bucket(b2_api, bucket_name_prefix, bucket_cleaner): }, ) yield bucket - bucket_cleaner.cleanup_bucket(bucket) + bucket_manager.clean_bucket(bucket) @pytest.fixture diff --git a/b2sdk/_internal/testing/helpers/api.py b/b2sdk/_internal/testing/helpers/api.py index a88c68edd..a02ded4ba 100644 --- a/b2sdk/_internal/testing/helpers/api.py +++ b/b2sdk/_internal/testing/helpers/api.py @@ -31,6 +31,7 @@ def get_b2_auth_data(): def authorize(b2_auth_data, api_config=DEFAULT_HTTP_API_CONFIG): info = InMemoryAccountInfo() + # TODO ME: Cache? b2_api = B2Api(info, api_config=api_config) realm = os.environ.get('B2_TEST_ENVIRONMENT', 'production') b2_api.authorize_account(*b2_auth_data, realm=realm) diff --git a/b2sdk/_internal/testing/helpers/base.py b/b2sdk/_internal/testing/helpers/base.py index 37fe39310..b61ec4670 100644 --- a/b2sdk/_internal/testing/helpers/base.py +++ b/b2sdk/_internal/testing/helpers/base.py @@ -13,7 +13,7 @@ from b2sdk.v2 import B2Api, current_time_millis from b2sdk.v2.exception import DuplicateBucketName -from b2sdk._internal.testing.helpers.bucket_cleaner import BucketCleaner +from b2sdk._internal.testing.helpers.bucket_manager import BucketManager from b2sdk._internal.testing.helpers.buckets import ( BUCKET_CREATED_AT_MILLIS, random_bucket_name, @@ -24,14 +24,14 @@ class IntegrationTestBase: b2_api: B2Api this_run_bucket_name_prefix: str - bucket_cleaner: BucketCleaner + bucket_manager: BucketManager @pytest.fixture(autouse=True, scope='class') - def cls_setup(self, request, b2_api, b2_auth_data, bucket_name_prefix, bucket_cleaner): + def cls_setup(self, request, b2_api, b2_auth_data, bucket_name_prefix, bucket_manager): cls = request.cls cls.b2_auth_data = b2_auth_data cls.this_run_bucket_name_prefix = bucket_name_prefix - cls.bucket_cleaner = bucket_cleaner + cls.bucket_manager = bucket_manager cls.b2_api = b2_api cls.info = b2_api.account_info @@ -40,7 +40,7 @@ def setup_method(self): self.buckets_created = [] yield for bucket in self.buckets_created: - self.bucket_cleaner.cleanup_bucket(bucket) + self.bucket_manager.clean_bucket(bucket) def generate_bucket_name(self): return random_bucket_name(self.this_run_bucket_name_prefix) diff --git a/b2sdk/_internal/testing/helpers/bucket_cleaner.py b/b2sdk/_internal/testing/helpers/bucket_cleaner.py deleted file mode 100644 index 29ca42126..000000000 --- a/b2sdk/_internal/testing/helpers/bucket_cleaner.py +++ /dev/null @@ -1,118 +0,0 @@ -###################################################################### -# -# File: test/integration/bucket_cleaner.py -# -# Copyright 2022 Backblaze Inc. All Rights Reserved. -# -# License https://www.backblaze.com/using_b2_code.html -# -###################################################################### -from __future__ import annotations - -import logging - -from b2sdk.v3 import ( - NO_RETENTION_FILE_SETTING, - B2Api, - Bucket, - LegalHold, - RetentionMode, - current_time_millis, -) -from b2sdk.v3.exception import BadRequest -from b2sdk._internal.testing.helpers.buckets import BUCKET_CREATED_AT_MILLIS, GENERAL_BUCKET_NAME_PREFIX - - -ONE_HOUR_MILLIS = 60 * 60 * 1000 - -logger = logging.getLogger(__name__) - - -class BucketCleaner: - def __init__( - self, dont_cleanup_old_buckets: bool, b2_api: B2Api, current_run_prefix: str | None = None - ): - self.current_run_prefix = current_run_prefix - self.dont_cleanup_old_buckets = dont_cleanup_old_buckets - self.b2_api = b2_api - - def _should_remove_bucket(self, bucket: Bucket): - if self.current_run_prefix and bucket.name.startswith(self.current_run_prefix): - return True - if self.dont_cleanup_old_buckets: - return False - if bucket.name.startswith(GENERAL_BUCKET_NAME_PREFIX): - if BUCKET_CREATED_AT_MILLIS in bucket.bucket_info: - if ( - int(bucket.bucket_info[BUCKET_CREATED_AT_MILLIS]) - < current_time_millis() - ONE_HOUR_MILLIS - ): - return True - return False - - def cleanup_buckets(self): - buckets = self.b2_api.list_buckets() - for bucket in buckets: - self.cleanup_bucket(bucket) - - def cleanup_bucket(self, bucket: Bucket): - b2_api = self.b2_api - if not self._should_remove_bucket(bucket): - logger.info('Skipping bucket removal:', bucket.name) - else: - logger.info('Trying to remove bucket:', bucket.name) - files_leftover = False - try: - b2_api.delete_bucket(bucket) - except BadRequest: - logger.info('Bucket is not empty, removing files') - files_leftover = True - - if files_leftover: - files_leftover = False - file_versions = bucket.ls(latest_only=False, recursive=True) - for file_version_info, _ in file_versions: - if file_version_info.file_retention: - if file_version_info.file_retention.mode == RetentionMode.GOVERNANCE: - logger.info( - 'Removing retention from file version: %s', file_version_info.id_ - ) - b2_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() - ): - logger.info( - 'File version: %s cannot be removed due to compliance mode retention', - file_version_info.id_, - ) - 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(): - logger.info( - 'Removing legal hold from file version: %s', file_version_info.id_ - ) - b2_api.update_file_legal_hold( - file_version_info.id_, file_version_info.file_name, LegalHold.OFF - ) - logger.info('Removing file version:', file_version_info.id_) - b2_api.delete_file_version(file_version_info.id_, file_version_info.file_name) - - if files_leftover: - logger.info('Unable to remove bucket because some retained files remain') - return - else: - b2_api.delete_bucket(bucket) - logger.info('Removed bucket:', bucket.name) diff --git a/b2sdk/_internal/testing/helpers/bucket_manager.py b/b2sdk/_internal/testing/helpers/bucket_manager.py new file mode 100644 index 000000000..28e00157c --- /dev/null +++ b/b2sdk/_internal/testing/helpers/bucket_manager.py @@ -0,0 +1,223 @@ +###################################################################### +# +# File: test/integration/bucket_manager.py +# +# Copyright 2022 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### +from __future__ import annotations + +import logging +import platform +from collections.abc import Iterable +from datetime import datetime, timedelta + +from itertools import chain +from typing import Any + +import tenacity + +from b2sdk._internal.exception import BucketIdNotFound, TooManyRequests, FileNotPresent +from b2sdk.v3 import ( + NO_RETENTION_FILE_SETTING, + B2Api, + Bucket, + LegalHold, + RetentionMode, + current_time_millis, +) +from b2sdk.v3.exception import BadRequest +from b2sdk._internal.testing.helpers.buckets import BUCKET_CREATED_AT_MILLIS, GENERAL_BUCKET_NAME_PREFIX, random_token, \ + BUCKET_NAME_LENGTH + + +NODE_DESCRIPTION = f'{platform.node()}: {platform.platform()}' +ONE_HOUR_MILLIS = 60 * 60 * 1000 +BUCKET_CLEANUP_PERIOD_MILLIS = timedelta(hours=3).total_seconds() * 1000 + +logger = logging.getLogger(__name__) + + +class BucketManager: + def __init__( + self, + dont_cleanup_old_buckets: bool, + b2_api: B2Api, + current_run_prefix: str, + general_prefix: str = GENERAL_BUCKET_NAME_PREFIX + ): + self.current_run_prefix = current_run_prefix + self.general_prefix = general_prefix + self.dont_cleanup_old_buckets = dont_cleanup_old_buckets + self.b2_api = b2_api + self.bucket_name_log: list[str] = [] + + def new_bucket_name(self) -> str: + bucket_name = self.current_run_prefix + random_token( + BUCKET_NAME_LENGTH - len(self.current_run_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.b2_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.current_run_prefix): + return True, 'it is a bucket for this very run' + if self.dont_cleanup_old_buckets: + return False, 'old buckets ought not to be cleaned' + if bucket.name.startswith(self.general_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_prefix!r}' + + def clean_buckets(self, quick=False): + # even with use_cache=True, if cache is empty API call will be made + buckets = self.b2_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: 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. + """ + if isinstance(bucket, str): + bucket = self.b2_api.get_bucket_by_name(bucket) + + if not only_files: + # try optimistic bucket removal first, since it is completely free (as opposed to `ls` call) + try: + return self.b2_api.delete_bucket(bucket) + except BucketIdNotFound: + return # bucket was already removed + except BadRequest as exc: + assert exc.code == 'cannot_delete_non_empty_bucket' + + files_leftover = False + + file_versions: Iterable[Any] + if only_folders: + file_versions = chain.from_iterable( + [ + 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.b2_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.b2_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.b2_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.b2_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.b2_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 diff --git a/b2sdk/_internal/testing/helpers/buckets.py b/b2sdk/_internal/testing/helpers/buckets.py index 0cd7da5e8..f31e1a6b5 100644 --- a/b2sdk/_internal/testing/helpers/buckets.py +++ b/b2sdk/_internal/testing/helpers/buckets.py @@ -23,13 +23,13 @@ RNG = secrets.SystemRandom() -def _bucket_name_prefix_part(length: int) -> str: - return ''.join(RNG.choice(BUCKET_NAME_CHARS_UNIQ) for _ in range(length)) +def random_token(length: int, chars: str = BUCKET_NAME_CHARS_UNIQ) -> str: + return ''.join(RNG.choice(chars) for _ in range(length)) def get_bucket_name_prefix(rnd_len: int = 8) -> str: - return GENERAL_BUCKET_NAME_PREFIX + _bucket_name_prefix_part(rnd_len) + return GENERAL_BUCKET_NAME_PREFIX + random_token(rnd_len) def random_bucket_name(prefix: str = GENERAL_BUCKET_NAME_PREFIX) -> str: - return prefix + _bucket_name_prefix_part(BUCKET_NAME_LENGTH - len(prefix)) \ No newline at end of file + return prefix + random_token(BUCKET_NAME_LENGTH - len(prefix)) \ No newline at end of file diff --git a/b2sdk/v0/testing/__init__.py b/b2sdk/v0/testing/__init__.py new file mode 100644 index 000000000..cf3b82254 --- /dev/null +++ b/b2sdk/v0/testing/__init__.py @@ -0,0 +1,12 @@ +###################################################################### +# +# File: b2sdk/v0/testing/__init__.py +# +# Copyright 2021 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### +from __future__ import annotations + +from b2sdk.v1.testing import * # noqa diff --git a/b2sdk/v1/testing/__init__.py b/b2sdk/v1/testing/__init__.py new file mode 100644 index 000000000..aef611260 --- /dev/null +++ b/b2sdk/v1/testing/__init__.py @@ -0,0 +1,12 @@ +###################################################################### +# +# File: b2sdk/v1/testing/__init__.py +# +# Copyright 2021 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### +from __future__ import annotations + +from b2sdk.v2.testing import * # noqa diff --git a/b2sdk/v2/testing/__init__.py b/b2sdk/v2/testing/__init__.py new file mode 100644 index 000000000..e05115a4f --- /dev/null +++ b/b2sdk/v2/testing/__init__.py @@ -0,0 +1,12 @@ +###################################################################### +# +# File: b2sdk/v2/testing/__init__.py +# +# Copyright 2021 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### +from __future__ import annotations + +from b2sdk.v3.testing import * # noqa diff --git a/b2sdk/v3/testing/__init__.py b/b2sdk/v3/testing/__init__.py new file mode 100644 index 000000000..f3c945388 --- /dev/null +++ b/b2sdk/v3/testing/__init__.py @@ -0,0 +1,48 @@ +###################################################################### +# +# File: b2sdk/v3/testing/__init__.py +# +# Copyright 2021 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### +from __future__ import annotations + +# testing - it is not imported in v3.__init__ as it depends on pytest and other test dependencies. + +from b2sdk._internal.testing.helpers.api import ( + get_b2_auth_data, + authorize +) +from b2sdk._internal.testing.helpers.base import IntegrationTestBase +from b2sdk._internal.testing.helpers.buckets import ( + GENERAL_BUCKET_NAME_PREFIX, + BUCKET_NAME_LENGTH, + BUCKET_CREATED_AT_MILLIS, + RNG, + random_token, + get_bucket_name_prefix, + random_bucket_name +) +from b2sdk._internal.testing.helpers.bucket_manager import ( + NODE_DESCRIPTION, + ONE_HOUR_MILLIS, + BUCKET_CLEANUP_PERIOD_MILLIS, + BucketManager +) +from b2sdk._internal.testing.fixtures.api import ( + set_http_debug, + b2_auth_data, + _b2_api, + b2_api +) +from b2sdk._internal.testing.fixtures.buckets import ( + pytest_addoption, + dont_cleanup_old_buckets, + bucket_name_prefix, + general_bucket_name_prefix, + bucket_manager, + bucket, + b2_subfolder +) diff --git a/pdm.lock b/pdm.lock index 729df2a28..a01308198 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "doc", "format", "lint", "release", "test"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:152fc7db21879c9f41e3420fb93207c95a841431b82e2d19cf4f7f8084edc030" +content_hash = "sha256:b918b87d89405588976c7e23236b7866df2162172d3fd4940df1db52b9e49f60" [[metadata.targets]] requires_python = ">=3.9" @@ -181,159 +181,201 @@ files = [ [[package]] name = "coverage" -version = "7.9.2" +version = "7.10.1" requires_python = ">=3.9" summary = "Code coverage measurement for Python" groups = ["test"] files = [ - {file = "coverage-7.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66283a192a14a3854b2e7f3418d7db05cdf411012ab7ff5db98ff3b181e1f912"}, - {file = "coverage-7.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e01d138540ef34fcf35c1aa24d06c3de2a4cffa349e29a10056544f35cca15f"}, - {file = "coverage-7.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f22627c1fe2745ee98d3ab87679ca73a97e75ca75eb5faee48660d060875465f"}, - {file = "coverage-7.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b1c2d8363247b46bd51f393f86c94096e64a1cf6906803fa8d5a9d03784bdbf"}, - {file = "coverage-7.9.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c10c882b114faf82dbd33e876d0cbd5e1d1ebc0d2a74ceef642c6152f3f4d547"}, - {file = "coverage-7.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:de3c0378bdf7066c3988d66cd5232d161e933b87103b014ab1b0b4676098fa45"}, - {file = "coverage-7.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1e2f097eae0e5991e7623958a24ced3282676c93c013dde41399ff63e230fcf2"}, - {file = "coverage-7.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28dc1f67e83a14e7079b6cea4d314bc8b24d1aed42d3582ff89c0295f09b181e"}, - {file = "coverage-7.9.2-cp310-cp310-win32.whl", hash = "sha256:bf7d773da6af9e10dbddacbf4e5cab13d06d0ed93561d44dae0188a42c65be7e"}, - {file = "coverage-7.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:0c0378ba787681ab1897f7c89b415bd56b0b2d9a47e5a3d8dc0ea55aac118d6c"}, - {file = "coverage-7.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a7a56a2964a9687b6aba5b5ced6971af308ef6f79a91043c05dd4ee3ebc3e9ba"}, - {file = "coverage-7.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123d589f32c11d9be7fe2e66d823a236fe759b0096f5db3fb1b75b2fa414a4fa"}, - {file = "coverage-7.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:333b2e0ca576a7dbd66e85ab402e35c03b0b22f525eed82681c4b866e2e2653a"}, - {file = "coverage-7.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:326802760da234baf9f2f85a39e4a4b5861b94f6c8d95251f699e4f73b1835dc"}, - {file = "coverage-7.9.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e7be4cfec248df38ce40968c95d3952fbffd57b400d4b9bb580f28179556d2"}, - {file = "coverage-7.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b4a4cb73b9f2b891c1788711408ef9707666501ba23684387277ededab1097c"}, - {file = "coverage-7.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2c8937fa16c8c9fbbd9f118588756e7bcdc7e16a470766a9aef912dd3f117dbd"}, - {file = "coverage-7.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:42da2280c4d30c57a9b578bafd1d4494fa6c056d4c419d9689e66d775539be74"}, - {file = "coverage-7.9.2-cp311-cp311-win32.whl", hash = "sha256:14fa8d3da147f5fdf9d298cacc18791818f3f1a9f542c8958b80c228320e90c6"}, - {file = "coverage-7.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:549cab4892fc82004f9739963163fd3aac7a7b0df430669b75b86d293d2df2a7"}, - {file = "coverage-7.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2667a2b913e307f06aa4e5677f01a9746cd08e4b35e14ebcde6420a9ebb4c62"}, - {file = "coverage-7.9.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae9eb07f1cfacd9cfe8eaee6f4ff4b8a289a668c39c165cd0c8548484920ffc0"}, - {file = "coverage-7.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ce85551f9a1119f02adc46d3014b5ee3f765deac166acf20dbb851ceb79b6f3"}, - {file = "coverage-7.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8f6389ac977c5fb322e0e38885fbbf901743f79d47f50db706e7644dcdcb6e1"}, - {file = "coverage-7.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d9eae8cdfcd58fe7893b88993723583a6ce4dfbfd9f29e001922544f95615"}, - {file = "coverage-7.9.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae939811e14e53ed8a9818dad51d434a41ee09df9305663735f2e2d2d7d959b"}, - {file = "coverage-7.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:31991156251ec202c798501e0a42bbdf2169dcb0f137b1f5c0f4267f3fc68ef9"}, - {file = "coverage-7.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d0d67963f9cbfc7c7f96d4ac74ed60ecbebd2ea6eeb51887af0f8dce205e545f"}, - {file = "coverage-7.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49b752a2858b10580969ec6af6f090a9a440a64a301ac1528d7ca5f7ed497f4d"}, - {file = "coverage-7.9.2-cp312-cp312-win32.whl", hash = "sha256:88d7598b8ee130f32f8a43198ee02edd16d7f77692fa056cb779616bbea1b355"}, - {file = "coverage-7.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:9dfb070f830739ee49d7c83e4941cc767e503e4394fdecb3b54bfdac1d7662c0"}, - {file = "coverage-7.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:4e2c058aef613e79df00e86b6d42a641c877211384ce5bd07585ed7ba71ab31b"}, - {file = "coverage-7.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038"}, - {file = "coverage-7.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d"}, - {file = "coverage-7.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3"}, - {file = "coverage-7.9.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14"}, - {file = "coverage-7.9.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6"}, - {file = "coverage-7.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b"}, - {file = "coverage-7.9.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d"}, - {file = "coverage-7.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868"}, - {file = "coverage-7.9.2-cp313-cp313-win32.whl", hash = "sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a"}, - {file = "coverage-7.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b"}, - {file = "coverage-7.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694"}, - {file = "coverage-7.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5"}, - {file = "coverage-7.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b"}, - {file = "coverage-7.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3"}, - {file = "coverage-7.9.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8"}, - {file = "coverage-7.9.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46"}, - {file = "coverage-7.9.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584"}, - {file = "coverage-7.9.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e"}, - {file = "coverage-7.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac"}, - {file = "coverage-7.9.2-cp313-cp313t-win32.whl", hash = "sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926"}, - {file = "coverage-7.9.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd"}, - {file = "coverage-7.9.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb"}, - {file = "coverage-7.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddc39510ac922a5c4c27849b739f875d3e1d9e590d1e7b64c98dadf037a16cce"}, - {file = "coverage-7.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a535c0c7364acd55229749c2b3e5eebf141865de3a8f697076a3291985f02d30"}, - {file = "coverage-7.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df0f9ef28e0f20c767ccdccfc5ae5f83a6f4a2fbdfbcbcc8487a8a78771168c8"}, - {file = "coverage-7.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f3da12e0ccbcb348969221d29441ac714bbddc4d74e13923d3d5a7a0bebef7a"}, - {file = "coverage-7.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a17eaf46f56ae0f870f14a3cbc2e4632fe3771eab7f687eda1ee59b73d09fe4"}, - {file = "coverage-7.9.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:669135a9d25df55d1ed56a11bf555f37c922cf08d80799d4f65d77d7d6123fcf"}, - {file = "coverage-7.9.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9d3a700304d01a627df9db4322dc082a0ce1e8fc74ac238e2af39ced4c083193"}, - {file = "coverage-7.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:71ae8b53855644a0b1579d4041304ddc9995c7b21c8a1f16753c4d8903b4dfed"}, - {file = "coverage-7.9.2-cp39-cp39-win32.whl", hash = "sha256:dd7a57b33b5cf27acb491e890720af45db05589a80c1ffc798462a765be6d4d7"}, - {file = "coverage-7.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f65bb452e579d5540c8b37ec105dd54d8b9307b07bcaa186818c104ffda22441"}, - {file = "coverage-7.9.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:8a1166db2fb62473285bcb092f586e081e92656c7dfa8e9f62b4d39d7e6b5050"}, - {file = "coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4"}, - {file = "coverage-7.9.2.tar.gz", hash = "sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b"}, + {file = "coverage-7.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1c86eb388bbd609d15560e7cc0eb936c102b6f43f31cf3e58b4fd9afe28e1372"}, + {file = "coverage-7.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b4ba0f488c1bdb6bd9ba81da50715a372119785458831c73428a8566253b86b"}, + {file = "coverage-7.10.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083442ecf97d434f0cb3b3e3676584443182653da08b42e965326ba12d6b5f2a"}, + {file = "coverage-7.10.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c1a40c486041006b135759f59189385da7c66d239bad897c994e18fd1d0c128f"}, + {file = "coverage-7.10.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3beb76e20b28046989300c4ea81bf690df84ee98ade4dc0bbbf774a28eb98440"}, + {file = "coverage-7.10.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc265a7945e8d08da28999ad02b544963f813a00f3ed0a7a0ce4165fd77629f8"}, + {file = "coverage-7.10.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:47c91f32ba4ac46f1e224a7ebf3f98b4b24335bad16137737fe71a5961a0665c"}, + {file = "coverage-7.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1a108dd78ed185020f66f131c60078f3fae3f61646c28c8bb4edd3fa121fc7fc"}, + {file = "coverage-7.10.1-cp310-cp310-win32.whl", hash = "sha256:7092cc82382e634075cc0255b0b69cb7cada7c1f249070ace6a95cb0f13548ef"}, + {file = "coverage-7.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:ac0c5bba938879c2fc0bc6c1b47311b5ad1212a9dcb8b40fe2c8110239b7faed"}, + {file = "coverage-7.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45e2f9d5b0b5c1977cb4feb5f594be60eb121106f8900348e29331f553a726f"}, + {file = "coverage-7.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a7a4d74cb0f5e3334f9aa26af7016ddb94fb4bfa11b4a573d8e98ecba8c34f1"}, + {file = "coverage-7.10.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d4b0aab55ad60ead26159ff12b538c85fbab731a5e3411c642b46c3525863437"}, + {file = "coverage-7.10.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dcc93488c9ebd229be6ee1f0d9aad90da97b33ad7e2912f5495804d78a3cd6b7"}, + {file = "coverage-7.10.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa309df995d020f3438407081b51ff527171cca6772b33cf8f85344b8b4b8770"}, + {file = "coverage-7.10.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cfb8b9d8855c8608f9747602a48ab525b1d320ecf0113994f6df23160af68262"}, + {file = "coverage-7.10.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:320d86da829b012982b414c7cdda65f5d358d63f764e0e4e54b33097646f39a3"}, + {file = "coverage-7.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dc60ddd483c556590da1d9482a4518292eec36dd0e1e8496966759a1f282bcd0"}, + {file = "coverage-7.10.1-cp311-cp311-win32.whl", hash = "sha256:4fcfe294f95b44e4754da5b58be750396f2b1caca8f9a0e78588e3ef85f8b8be"}, + {file = "coverage-7.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:efa23166da3fe2915f8ab452dde40319ac84dc357f635737174a08dbd912980c"}, + {file = "coverage-7.10.1-cp311-cp311-win_arm64.whl", hash = "sha256:d12b15a8c3759e2bb580ffa423ae54be4f184cf23beffcbd641f4fe6e1584293"}, + {file = "coverage-7.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6b7dc7f0a75a7eaa4584e5843c873c561b12602439d2351ee28c7478186c4da4"}, + {file = "coverage-7.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:607f82389f0ecafc565813aa201a5cade04f897603750028dd660fb01797265e"}, + {file = "coverage-7.10.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f7da31a1ba31f1c1d4d5044b7c5813878adae1f3af8f4052d679cc493c7328f4"}, + {file = "coverage-7.10.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51fe93f3fe4f5d8483d51072fddc65e717a175490804e1942c975a68e04bf97a"}, + {file = "coverage-7.10.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e59d00830da411a1feef6ac828b90bbf74c9b6a8e87b8ca37964925bba76dbe"}, + {file = "coverage-7.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:924563481c27941229cb4e16eefacc35da28563e80791b3ddc5597b062a5c386"}, + {file = "coverage-7.10.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ca79146ee421b259f8131f153102220b84d1a5e6fb9c8aed13b3badfd1796de6"}, + {file = "coverage-7.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b225a06d227f23f386fdc0eab471506d9e644be699424814acc7d114595495f"}, + {file = "coverage-7.10.1-cp312-cp312-win32.whl", hash = "sha256:5ba9a8770effec5baaaab1567be916c87d8eea0c9ad11253722d86874d885eca"}, + {file = "coverage-7.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:9eb245a8d8dd0ad73b4062135a251ec55086fbc2c42e0eb9725a9b553fba18a3"}, + {file = "coverage-7.10.1-cp312-cp312-win_arm64.whl", hash = "sha256:7718060dd4434cc719803a5e526838a5d66e4efa5dc46d2b25c21965a9c6fcc4"}, + {file = "coverage-7.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebb08d0867c5a25dffa4823377292a0ffd7aaafb218b5d4e2e106378b1061e39"}, + {file = "coverage-7.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f32a95a83c2e17422f67af922a89422cd24c6fa94041f083dd0bb4f6057d0bc7"}, + {file = "coverage-7.10.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c4c746d11c8aba4b9f58ca8bfc6fbfd0da4efe7960ae5540d1a1b13655ee8892"}, + {file = "coverage-7.10.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7f39edd52c23e5c7ed94e0e4bf088928029edf86ef10b95413e5ea670c5e92d7"}, + {file = "coverage-7.10.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab6e19b684981d0cd968906e293d5628e89faacb27977c92f3600b201926b994"}, + {file = "coverage-7.10.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5121d8cf0eacb16133501455d216bb5f99899ae2f52d394fe45d59229e6611d0"}, + {file = "coverage-7.10.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df1c742ca6f46a6f6cbcaef9ac694dc2cb1260d30a6a2f5c68c5f5bcfee1cfd7"}, + {file = "coverage-7.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40f9a38676f9c073bf4b9194707aa1eb97dca0e22cc3766d83879d72500132c7"}, + {file = "coverage-7.10.1-cp313-cp313-win32.whl", hash = "sha256:2348631f049e884839553b9974f0821d39241c6ffb01a418efce434f7eba0fe7"}, + {file = "coverage-7.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:4072b31361b0d6d23f750c524f694e1a417c1220a30d3ef02741eed28520c48e"}, + {file = "coverage-7.10.1-cp313-cp313-win_arm64.whl", hash = "sha256:3e31dfb8271937cab9425f19259b1b1d1f556790e98eb266009e7a61d337b6d4"}, + {file = "coverage-7.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1c4f679c6b573a5257af6012f167a45be4c749c9925fd44d5178fd641ad8bf72"}, + {file = "coverage-7.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:871ebe8143da284bd77b84a9136200bd638be253618765d21a1fce71006d94af"}, + {file = "coverage-7.10.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:998c4751dabf7d29b30594af416e4bf5091f11f92a8d88eb1512c7ba136d1ed7"}, + {file = "coverage-7.10.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:780f750a25e7749d0af6b3631759c2c14f45de209f3faaa2398312d1c7a22759"}, + {file = "coverage-7.10.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:590bdba9445df4763bdbebc928d8182f094c1f3947a8dc0fc82ef014dbdd8324"}, + {file = "coverage-7.10.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b2df80cb6a2af86d300e70acb82e9b79dab2c1e6971e44b78dbfc1a1e736b53"}, + {file = "coverage-7.10.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d6a558c2725bfb6337bf57c1cd366c13798bfd3bfc9e3dd1f4a6f6fc95a4605f"}, + {file = "coverage-7.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e6150d167f32f2a54690e572e0a4c90296fb000a18e9b26ab81a6489e24e78dd"}, + {file = "coverage-7.10.1-cp313-cp313t-win32.whl", hash = "sha256:d946a0c067aa88be4a593aad1236493313bafaa27e2a2080bfe88db827972f3c"}, + {file = "coverage-7.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e37c72eaccdd5ed1130c67a92ad38f5b2af66eeff7b0abe29534225db2ef7b18"}, + {file = "coverage-7.10.1-cp313-cp313t-win_arm64.whl", hash = "sha256:89ec0ffc215c590c732918c95cd02b55c7d0f569d76b90bb1a5e78aa340618e4"}, + {file = "coverage-7.10.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:166d89c57e877e93d8827dac32cedae6b0277ca684c6511497311249f35a280c"}, + {file = "coverage-7.10.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bed4a2341b33cd1a7d9ffc47df4a78ee61d3416d43b4adc9e18b7d266650b83e"}, + {file = "coverage-7.10.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ddca1e4f5f4c67980533df01430184c19b5359900e080248bbf4ed6789584d8b"}, + {file = "coverage-7.10.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:37b69226001d8b7de7126cad7366b0778d36777e4d788c66991455ba817c5b41"}, + {file = "coverage-7.10.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2f22102197bcb1722691296f9e589f02b616f874e54a209284dd7b9294b0b7f"}, + {file = "coverage-7.10.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1e0c768b0f9ac5839dac5cf88992a4bb459e488ee8a1f8489af4cb33b1af00f1"}, + {file = "coverage-7.10.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:991196702d5e0b120a8fef2664e1b9c333a81d36d5f6bcf6b225c0cf8b0451a2"}, + {file = "coverage-7.10.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae8e59e5f4fd85d6ad34c2bb9d74037b5b11be072b8b7e9986beb11f957573d4"}, + {file = "coverage-7.10.1-cp314-cp314-win32.whl", hash = "sha256:042125c89cf74a074984002e165d61fe0e31c7bd40ebb4bbebf07939b5924613"}, + {file = "coverage-7.10.1-cp314-cp314-win_amd64.whl", hash = "sha256:a22c3bfe09f7a530e2c94c87ff7af867259c91bef87ed2089cd69b783af7b84e"}, + {file = "coverage-7.10.1-cp314-cp314-win_arm64.whl", hash = "sha256:ee6be07af68d9c4fca4027c70cea0c31a0f1bc9cb464ff3c84a1f916bf82e652"}, + {file = "coverage-7.10.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d24fb3c0c8ff0d517c5ca5de7cf3994a4cd559cde0315201511dbfa7ab528894"}, + {file = "coverage-7.10.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1217a54cfd79be20512a67ca81c7da3f2163f51bbfd188aab91054df012154f5"}, + {file = "coverage-7.10.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:51f30da7a52c009667e02f125737229d7d8044ad84b79db454308033a7808ab2"}, + {file = "coverage-7.10.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ed3718c757c82d920f1c94089066225ca2ad7f00bb904cb72b1c39ebdd906ccb"}, + {file = "coverage-7.10.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc452481e124a819ced0c25412ea2e144269ef2f2534b862d9f6a9dae4bda17b"}, + {file = "coverage-7.10.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9d6f494c307e5cb9b1e052ec1a471060f1dea092c8116e642e7a23e79d9388ea"}, + {file = "coverage-7.10.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fc0e46d86905ddd16b85991f1f4919028092b4e511689bbdaff0876bd8aab3dd"}, + {file = "coverage-7.10.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80b9ccd82e30038b61fc9a692a8dc4801504689651b281ed9109f10cc9fe8b4d"}, + {file = "coverage-7.10.1-cp314-cp314t-win32.whl", hash = "sha256:e58991a2b213417285ec866d3cd32db17a6a88061a985dbb7e8e8f13af429c47"}, + {file = "coverage-7.10.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e88dd71e4ecbc49d9d57d064117462c43f40a21a1383507811cf834a4a620651"}, + {file = "coverage-7.10.1-cp314-cp314t-win_arm64.whl", hash = "sha256:1aadfb06a30c62c2eb82322171fe1f7c288c80ca4156d46af0ca039052814bab"}, + {file = "coverage-7.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:57b6e8789cbefdef0667e4a94f8ffa40f9402cee5fc3b8e4274c894737890145"}, + {file = "coverage-7.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85b22a9cce00cb03156334da67eb86e29f22b5e93876d0dd6a98646bb8a74e53"}, + {file = "coverage-7.10.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:97b6983a2f9c76d345ca395e843a049390b39652984e4a3b45b2442fa733992d"}, + {file = "coverage-7.10.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ddf2a63b91399a1c2f88f40bc1705d5a7777e31c7e9eb27c602280f477b582ba"}, + {file = "coverage-7.10.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47ab6dbbc31a14c5486420c2c1077fcae692097f673cf5be9ddbec8cdaa4cdbc"}, + {file = "coverage-7.10.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:21eb7d8b45d3700e7c2936a736f732794c47615a20f739f4133d5230a6512a88"}, + {file = "coverage-7.10.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:283005bb4d98ae33e45f2861cd2cde6a21878661c9ad49697f6951b358a0379b"}, + {file = "coverage-7.10.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fefe31d61d02a8b2c419700b1fade9784a43d726de26495f243b663cd9fe1513"}, + {file = "coverage-7.10.1-cp39-cp39-win32.whl", hash = "sha256:e8ab8e4c7ec7f8a55ac05b5b715a051d74eac62511c6d96d5bb79aaafa3b04cf"}, + {file = "coverage-7.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:c36baa0ecde742784aa76c2b816466d3ea888d5297fda0edbac1bf48fa94688a"}, + {file = "coverage-7.10.1-py3-none-any.whl", hash = "sha256:fa2a258aa6bf188eb9a8948f7102a83da7c430a0dce918dbd8b60ef8fcb772d7"}, + {file = "coverage-7.10.1.tar.gz", hash = "sha256:ae2b4856f29ddfe827106794f3589949a57da6f0d38ab01e24ec35107979ba57"}, ] [[package]] name = "coverage" -version = "7.9.2" +version = "7.10.1" extras = ["toml"] requires_python = ">=3.9" summary = "Code coverage measurement for Python" groups = ["test"] dependencies = [ - "coverage==7.9.2", + "coverage==7.10.1", "tomli; python_full_version <= \"3.11.0a6\"", ] files = [ - {file = "coverage-7.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66283a192a14a3854b2e7f3418d7db05cdf411012ab7ff5db98ff3b181e1f912"}, - {file = "coverage-7.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e01d138540ef34fcf35c1aa24d06c3de2a4cffa349e29a10056544f35cca15f"}, - {file = "coverage-7.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f22627c1fe2745ee98d3ab87679ca73a97e75ca75eb5faee48660d060875465f"}, - {file = "coverage-7.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b1c2d8363247b46bd51f393f86c94096e64a1cf6906803fa8d5a9d03784bdbf"}, - {file = "coverage-7.9.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c10c882b114faf82dbd33e876d0cbd5e1d1ebc0d2a74ceef642c6152f3f4d547"}, - {file = "coverage-7.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:de3c0378bdf7066c3988d66cd5232d161e933b87103b014ab1b0b4676098fa45"}, - {file = "coverage-7.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1e2f097eae0e5991e7623958a24ced3282676c93c013dde41399ff63e230fcf2"}, - {file = "coverage-7.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28dc1f67e83a14e7079b6cea4d314bc8b24d1aed42d3582ff89c0295f09b181e"}, - {file = "coverage-7.9.2-cp310-cp310-win32.whl", hash = "sha256:bf7d773da6af9e10dbddacbf4e5cab13d06d0ed93561d44dae0188a42c65be7e"}, - {file = "coverage-7.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:0c0378ba787681ab1897f7c89b415bd56b0b2d9a47e5a3d8dc0ea55aac118d6c"}, - {file = "coverage-7.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a7a56a2964a9687b6aba5b5ced6971af308ef6f79a91043c05dd4ee3ebc3e9ba"}, - {file = "coverage-7.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123d589f32c11d9be7fe2e66d823a236fe759b0096f5db3fb1b75b2fa414a4fa"}, - {file = "coverage-7.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:333b2e0ca576a7dbd66e85ab402e35c03b0b22f525eed82681c4b866e2e2653a"}, - {file = "coverage-7.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:326802760da234baf9f2f85a39e4a4b5861b94f6c8d95251f699e4f73b1835dc"}, - {file = "coverage-7.9.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e7be4cfec248df38ce40968c95d3952fbffd57b400d4b9bb580f28179556d2"}, - {file = "coverage-7.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b4a4cb73b9f2b891c1788711408ef9707666501ba23684387277ededab1097c"}, - {file = "coverage-7.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2c8937fa16c8c9fbbd9f118588756e7bcdc7e16a470766a9aef912dd3f117dbd"}, - {file = "coverage-7.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:42da2280c4d30c57a9b578bafd1d4494fa6c056d4c419d9689e66d775539be74"}, - {file = "coverage-7.9.2-cp311-cp311-win32.whl", hash = "sha256:14fa8d3da147f5fdf9d298cacc18791818f3f1a9f542c8958b80c228320e90c6"}, - {file = "coverage-7.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:549cab4892fc82004f9739963163fd3aac7a7b0df430669b75b86d293d2df2a7"}, - {file = "coverage-7.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2667a2b913e307f06aa4e5677f01a9746cd08e4b35e14ebcde6420a9ebb4c62"}, - {file = "coverage-7.9.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae9eb07f1cfacd9cfe8eaee6f4ff4b8a289a668c39c165cd0c8548484920ffc0"}, - {file = "coverage-7.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ce85551f9a1119f02adc46d3014b5ee3f765deac166acf20dbb851ceb79b6f3"}, - {file = "coverage-7.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8f6389ac977c5fb322e0e38885fbbf901743f79d47f50db706e7644dcdcb6e1"}, - {file = "coverage-7.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d9eae8cdfcd58fe7893b88993723583a6ce4dfbfd9f29e001922544f95615"}, - {file = "coverage-7.9.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae939811e14e53ed8a9818dad51d434a41ee09df9305663735f2e2d2d7d959b"}, - {file = "coverage-7.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:31991156251ec202c798501e0a42bbdf2169dcb0f137b1f5c0f4267f3fc68ef9"}, - {file = "coverage-7.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d0d67963f9cbfc7c7f96d4ac74ed60ecbebd2ea6eeb51887af0f8dce205e545f"}, - {file = "coverage-7.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49b752a2858b10580969ec6af6f090a9a440a64a301ac1528d7ca5f7ed497f4d"}, - {file = "coverage-7.9.2-cp312-cp312-win32.whl", hash = "sha256:88d7598b8ee130f32f8a43198ee02edd16d7f77692fa056cb779616bbea1b355"}, - {file = "coverage-7.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:9dfb070f830739ee49d7c83e4941cc767e503e4394fdecb3b54bfdac1d7662c0"}, - {file = "coverage-7.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:4e2c058aef613e79df00e86b6d42a641c877211384ce5bd07585ed7ba71ab31b"}, - {file = "coverage-7.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038"}, - {file = "coverage-7.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d"}, - {file = "coverage-7.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3"}, - {file = "coverage-7.9.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14"}, - {file = "coverage-7.9.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6"}, - {file = "coverage-7.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b"}, - {file = "coverage-7.9.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d"}, - {file = "coverage-7.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868"}, - {file = "coverage-7.9.2-cp313-cp313-win32.whl", hash = "sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a"}, - {file = "coverage-7.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b"}, - {file = "coverage-7.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694"}, - {file = "coverage-7.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5"}, - {file = "coverage-7.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b"}, - {file = "coverage-7.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3"}, - {file = "coverage-7.9.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8"}, - {file = "coverage-7.9.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46"}, - {file = "coverage-7.9.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584"}, - {file = "coverage-7.9.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e"}, - {file = "coverage-7.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac"}, - {file = "coverage-7.9.2-cp313-cp313t-win32.whl", hash = "sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926"}, - {file = "coverage-7.9.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd"}, - {file = "coverage-7.9.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb"}, - {file = "coverage-7.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddc39510ac922a5c4c27849b739f875d3e1d9e590d1e7b64c98dadf037a16cce"}, - {file = "coverage-7.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a535c0c7364acd55229749c2b3e5eebf141865de3a8f697076a3291985f02d30"}, - {file = "coverage-7.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df0f9ef28e0f20c767ccdccfc5ae5f83a6f4a2fbdfbcbcc8487a8a78771168c8"}, - {file = "coverage-7.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f3da12e0ccbcb348969221d29441ac714bbddc4d74e13923d3d5a7a0bebef7a"}, - {file = "coverage-7.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a17eaf46f56ae0f870f14a3cbc2e4632fe3771eab7f687eda1ee59b73d09fe4"}, - {file = "coverage-7.9.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:669135a9d25df55d1ed56a11bf555f37c922cf08d80799d4f65d77d7d6123fcf"}, - {file = "coverage-7.9.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9d3a700304d01a627df9db4322dc082a0ce1e8fc74ac238e2af39ced4c083193"}, - {file = "coverage-7.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:71ae8b53855644a0b1579d4041304ddc9995c7b21c8a1f16753c4d8903b4dfed"}, - {file = "coverage-7.9.2-cp39-cp39-win32.whl", hash = "sha256:dd7a57b33b5cf27acb491e890720af45db05589a80c1ffc798462a765be6d4d7"}, - {file = "coverage-7.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f65bb452e579d5540c8b37ec105dd54d8b9307b07bcaa186818c104ffda22441"}, - {file = "coverage-7.9.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:8a1166db2fb62473285bcb092f586e081e92656c7dfa8e9f62b4d39d7e6b5050"}, - {file = "coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4"}, - {file = "coverage-7.9.2.tar.gz", hash = "sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b"}, + {file = "coverage-7.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1c86eb388bbd609d15560e7cc0eb936c102b6f43f31cf3e58b4fd9afe28e1372"}, + {file = "coverage-7.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b4ba0f488c1bdb6bd9ba81da50715a372119785458831c73428a8566253b86b"}, + {file = "coverage-7.10.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083442ecf97d434f0cb3b3e3676584443182653da08b42e965326ba12d6b5f2a"}, + {file = "coverage-7.10.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c1a40c486041006b135759f59189385da7c66d239bad897c994e18fd1d0c128f"}, + {file = "coverage-7.10.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3beb76e20b28046989300c4ea81bf690df84ee98ade4dc0bbbf774a28eb98440"}, + {file = "coverage-7.10.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc265a7945e8d08da28999ad02b544963f813a00f3ed0a7a0ce4165fd77629f8"}, + {file = "coverage-7.10.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:47c91f32ba4ac46f1e224a7ebf3f98b4b24335bad16137737fe71a5961a0665c"}, + {file = "coverage-7.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1a108dd78ed185020f66f131c60078f3fae3f61646c28c8bb4edd3fa121fc7fc"}, + {file = "coverage-7.10.1-cp310-cp310-win32.whl", hash = "sha256:7092cc82382e634075cc0255b0b69cb7cada7c1f249070ace6a95cb0f13548ef"}, + {file = "coverage-7.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:ac0c5bba938879c2fc0bc6c1b47311b5ad1212a9dcb8b40fe2c8110239b7faed"}, + {file = "coverage-7.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45e2f9d5b0b5c1977cb4feb5f594be60eb121106f8900348e29331f553a726f"}, + {file = "coverage-7.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a7a4d74cb0f5e3334f9aa26af7016ddb94fb4bfa11b4a573d8e98ecba8c34f1"}, + {file = "coverage-7.10.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d4b0aab55ad60ead26159ff12b538c85fbab731a5e3411c642b46c3525863437"}, + {file = "coverage-7.10.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dcc93488c9ebd229be6ee1f0d9aad90da97b33ad7e2912f5495804d78a3cd6b7"}, + {file = "coverage-7.10.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa309df995d020f3438407081b51ff527171cca6772b33cf8f85344b8b4b8770"}, + {file = "coverage-7.10.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cfb8b9d8855c8608f9747602a48ab525b1d320ecf0113994f6df23160af68262"}, + {file = "coverage-7.10.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:320d86da829b012982b414c7cdda65f5d358d63f764e0e4e54b33097646f39a3"}, + {file = "coverage-7.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dc60ddd483c556590da1d9482a4518292eec36dd0e1e8496966759a1f282bcd0"}, + {file = "coverage-7.10.1-cp311-cp311-win32.whl", hash = "sha256:4fcfe294f95b44e4754da5b58be750396f2b1caca8f9a0e78588e3ef85f8b8be"}, + {file = "coverage-7.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:efa23166da3fe2915f8ab452dde40319ac84dc357f635737174a08dbd912980c"}, + {file = "coverage-7.10.1-cp311-cp311-win_arm64.whl", hash = "sha256:d12b15a8c3759e2bb580ffa423ae54be4f184cf23beffcbd641f4fe6e1584293"}, + {file = "coverage-7.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6b7dc7f0a75a7eaa4584e5843c873c561b12602439d2351ee28c7478186c4da4"}, + {file = "coverage-7.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:607f82389f0ecafc565813aa201a5cade04f897603750028dd660fb01797265e"}, + {file = "coverage-7.10.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f7da31a1ba31f1c1d4d5044b7c5813878adae1f3af8f4052d679cc493c7328f4"}, + {file = "coverage-7.10.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51fe93f3fe4f5d8483d51072fddc65e717a175490804e1942c975a68e04bf97a"}, + {file = "coverage-7.10.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e59d00830da411a1feef6ac828b90bbf74c9b6a8e87b8ca37964925bba76dbe"}, + {file = "coverage-7.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:924563481c27941229cb4e16eefacc35da28563e80791b3ddc5597b062a5c386"}, + {file = "coverage-7.10.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ca79146ee421b259f8131f153102220b84d1a5e6fb9c8aed13b3badfd1796de6"}, + {file = "coverage-7.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b225a06d227f23f386fdc0eab471506d9e644be699424814acc7d114595495f"}, + {file = "coverage-7.10.1-cp312-cp312-win32.whl", hash = "sha256:5ba9a8770effec5baaaab1567be916c87d8eea0c9ad11253722d86874d885eca"}, + {file = "coverage-7.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:9eb245a8d8dd0ad73b4062135a251ec55086fbc2c42e0eb9725a9b553fba18a3"}, + {file = "coverage-7.10.1-cp312-cp312-win_arm64.whl", hash = "sha256:7718060dd4434cc719803a5e526838a5d66e4efa5dc46d2b25c21965a9c6fcc4"}, + {file = "coverage-7.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebb08d0867c5a25dffa4823377292a0ffd7aaafb218b5d4e2e106378b1061e39"}, + {file = "coverage-7.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f32a95a83c2e17422f67af922a89422cd24c6fa94041f083dd0bb4f6057d0bc7"}, + {file = "coverage-7.10.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c4c746d11c8aba4b9f58ca8bfc6fbfd0da4efe7960ae5540d1a1b13655ee8892"}, + {file = "coverage-7.10.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7f39edd52c23e5c7ed94e0e4bf088928029edf86ef10b95413e5ea670c5e92d7"}, + {file = "coverage-7.10.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab6e19b684981d0cd968906e293d5628e89faacb27977c92f3600b201926b994"}, + {file = "coverage-7.10.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5121d8cf0eacb16133501455d216bb5f99899ae2f52d394fe45d59229e6611d0"}, + {file = "coverage-7.10.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df1c742ca6f46a6f6cbcaef9ac694dc2cb1260d30a6a2f5c68c5f5bcfee1cfd7"}, + {file = "coverage-7.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40f9a38676f9c073bf4b9194707aa1eb97dca0e22cc3766d83879d72500132c7"}, + {file = "coverage-7.10.1-cp313-cp313-win32.whl", hash = "sha256:2348631f049e884839553b9974f0821d39241c6ffb01a418efce434f7eba0fe7"}, + {file = "coverage-7.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:4072b31361b0d6d23f750c524f694e1a417c1220a30d3ef02741eed28520c48e"}, + {file = "coverage-7.10.1-cp313-cp313-win_arm64.whl", hash = "sha256:3e31dfb8271937cab9425f19259b1b1d1f556790e98eb266009e7a61d337b6d4"}, + {file = "coverage-7.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1c4f679c6b573a5257af6012f167a45be4c749c9925fd44d5178fd641ad8bf72"}, + {file = "coverage-7.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:871ebe8143da284bd77b84a9136200bd638be253618765d21a1fce71006d94af"}, + {file = "coverage-7.10.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:998c4751dabf7d29b30594af416e4bf5091f11f92a8d88eb1512c7ba136d1ed7"}, + {file = "coverage-7.10.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:780f750a25e7749d0af6b3631759c2c14f45de209f3faaa2398312d1c7a22759"}, + {file = "coverage-7.10.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:590bdba9445df4763bdbebc928d8182f094c1f3947a8dc0fc82ef014dbdd8324"}, + {file = "coverage-7.10.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b2df80cb6a2af86d300e70acb82e9b79dab2c1e6971e44b78dbfc1a1e736b53"}, + {file = "coverage-7.10.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d6a558c2725bfb6337bf57c1cd366c13798bfd3bfc9e3dd1f4a6f6fc95a4605f"}, + {file = "coverage-7.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e6150d167f32f2a54690e572e0a4c90296fb000a18e9b26ab81a6489e24e78dd"}, + {file = "coverage-7.10.1-cp313-cp313t-win32.whl", hash = "sha256:d946a0c067aa88be4a593aad1236493313bafaa27e2a2080bfe88db827972f3c"}, + {file = "coverage-7.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e37c72eaccdd5ed1130c67a92ad38f5b2af66eeff7b0abe29534225db2ef7b18"}, + {file = "coverage-7.10.1-cp313-cp313t-win_arm64.whl", hash = "sha256:89ec0ffc215c590c732918c95cd02b55c7d0f569d76b90bb1a5e78aa340618e4"}, + {file = "coverage-7.10.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:166d89c57e877e93d8827dac32cedae6b0277ca684c6511497311249f35a280c"}, + {file = "coverage-7.10.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bed4a2341b33cd1a7d9ffc47df4a78ee61d3416d43b4adc9e18b7d266650b83e"}, + {file = "coverage-7.10.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ddca1e4f5f4c67980533df01430184c19b5359900e080248bbf4ed6789584d8b"}, + {file = "coverage-7.10.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:37b69226001d8b7de7126cad7366b0778d36777e4d788c66991455ba817c5b41"}, + {file = "coverage-7.10.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2f22102197bcb1722691296f9e589f02b616f874e54a209284dd7b9294b0b7f"}, + {file = "coverage-7.10.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1e0c768b0f9ac5839dac5cf88992a4bb459e488ee8a1f8489af4cb33b1af00f1"}, + {file = "coverage-7.10.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:991196702d5e0b120a8fef2664e1b9c333a81d36d5f6bcf6b225c0cf8b0451a2"}, + {file = "coverage-7.10.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae8e59e5f4fd85d6ad34c2bb9d74037b5b11be072b8b7e9986beb11f957573d4"}, + {file = "coverage-7.10.1-cp314-cp314-win32.whl", hash = "sha256:042125c89cf74a074984002e165d61fe0e31c7bd40ebb4bbebf07939b5924613"}, + {file = "coverage-7.10.1-cp314-cp314-win_amd64.whl", hash = "sha256:a22c3bfe09f7a530e2c94c87ff7af867259c91bef87ed2089cd69b783af7b84e"}, + {file = "coverage-7.10.1-cp314-cp314-win_arm64.whl", hash = "sha256:ee6be07af68d9c4fca4027c70cea0c31a0f1bc9cb464ff3c84a1f916bf82e652"}, + {file = "coverage-7.10.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d24fb3c0c8ff0d517c5ca5de7cf3994a4cd559cde0315201511dbfa7ab528894"}, + {file = "coverage-7.10.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1217a54cfd79be20512a67ca81c7da3f2163f51bbfd188aab91054df012154f5"}, + {file = "coverage-7.10.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:51f30da7a52c009667e02f125737229d7d8044ad84b79db454308033a7808ab2"}, + {file = "coverage-7.10.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ed3718c757c82d920f1c94089066225ca2ad7f00bb904cb72b1c39ebdd906ccb"}, + {file = "coverage-7.10.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc452481e124a819ced0c25412ea2e144269ef2f2534b862d9f6a9dae4bda17b"}, + {file = "coverage-7.10.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9d6f494c307e5cb9b1e052ec1a471060f1dea092c8116e642e7a23e79d9388ea"}, + {file = "coverage-7.10.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fc0e46d86905ddd16b85991f1f4919028092b4e511689bbdaff0876bd8aab3dd"}, + {file = "coverage-7.10.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80b9ccd82e30038b61fc9a692a8dc4801504689651b281ed9109f10cc9fe8b4d"}, + {file = "coverage-7.10.1-cp314-cp314t-win32.whl", hash = "sha256:e58991a2b213417285ec866d3cd32db17a6a88061a985dbb7e8e8f13af429c47"}, + {file = "coverage-7.10.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e88dd71e4ecbc49d9d57d064117462c43f40a21a1383507811cf834a4a620651"}, + {file = "coverage-7.10.1-cp314-cp314t-win_arm64.whl", hash = "sha256:1aadfb06a30c62c2eb82322171fe1f7c288c80ca4156d46af0ca039052814bab"}, + {file = "coverage-7.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:57b6e8789cbefdef0667e4a94f8ffa40f9402cee5fc3b8e4274c894737890145"}, + {file = "coverage-7.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85b22a9cce00cb03156334da67eb86e29f22b5e93876d0dd6a98646bb8a74e53"}, + {file = "coverage-7.10.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:97b6983a2f9c76d345ca395e843a049390b39652984e4a3b45b2442fa733992d"}, + {file = "coverage-7.10.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ddf2a63b91399a1c2f88f40bc1705d5a7777e31c7e9eb27c602280f477b582ba"}, + {file = "coverage-7.10.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47ab6dbbc31a14c5486420c2c1077fcae692097f673cf5be9ddbec8cdaa4cdbc"}, + {file = "coverage-7.10.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:21eb7d8b45d3700e7c2936a736f732794c47615a20f739f4133d5230a6512a88"}, + {file = "coverage-7.10.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:283005bb4d98ae33e45f2861cd2cde6a21878661c9ad49697f6951b358a0379b"}, + {file = "coverage-7.10.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fefe31d61d02a8b2c419700b1fade9784a43d726de26495f243b663cd9fe1513"}, + {file = "coverage-7.10.1-cp39-cp39-win32.whl", hash = "sha256:e8ab8e4c7ec7f8a55ac05b5b715a051d74eac62511c6d96d5bb79aaafa3b04cf"}, + {file = "coverage-7.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:c36baa0ecde742784aa76c2b816466d3ea888d5297fda0edbac1bf48fa94688a"}, + {file = "coverage-7.10.1-py3-none-any.whl", hash = "sha256:fa2a258aa6bf188eb9a8948f7102a83da7c430a0dce918dbd8b60ef8fcb772d7"}, + {file = "coverage-7.10.1.tar.gz", hash = "sha256:ae2b4856f29ddfe827106794f3589949a57da6f0d38ab01e24ec35107979ba57"}, ] [[package]] @@ -1273,6 +1315,17 @@ files = [ {file = "starlette-0.47.1.tar.gz", hash = "sha256:aef012dd2b6be325ffa16698f9dc533614fb1cebd593a906b90dc1025529a79b"}, ] +[[package]] +name = "tenacity" +version = "9.1.2" +requires_python = ">=3.9" +summary = "Retry code until it succeeds" +groups = ["test"] +files = [ + {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, + {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, +] + [[package]] name = "toml" version = "0.10.2" diff --git a/pyproject.toml b/pyproject.toml index 24a1c1345..c0a87feed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -168,6 +168,7 @@ test = [ "pytest-lazy-fixtures==1.1.1", "pytest-xdist>=2.5.0", "pytest-timeout>=2.1.0", + "tenacity>=9.1.2", "tqdm<5.0.0,>=4.5.0", "eval_type_backport>=0.1.3,<1; python_version<'3.10'", # used with pydantic "pydantic>=2.0.1", diff --git a/test/integration/__init__.py b/test/integration/__init__.py index 8ce3a867d..a85eda93c 100644 --- a/test/integration/__init__.py +++ b/test/integration/__init__.py @@ -8,15 +8,3 @@ # ###################################################################### from __future__ import annotations -import os - - -def get_b2_auth_data(): - application_key_id = os.environ.get('B2_TEST_APPLICATION_KEY_ID') - if application_key_id is None: - raise ValueError('B2_TEST_APPLICATION_KEY_ID is not set.') - - application_key = os.environ.get('B2_TEST_APPLICATION_KEY') - if application_key is None: - raise ValueError('B2_TEST_APPLICATION_KEY is not set.') - return application_key_id, application_key diff --git a/test/integration/conftest.py b/test/integration/conftest.py index 3e8de637f..6d430ef16 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -7,17 +7,17 @@ # License https://www.backblaze.com/using_b2_code.html # ###################################################################### -from b2sdk._internal.testing.fixtures.api import ( # noqa: F401 - set_http_debug, - b2_auth_data, - _b2_api, - b2_api, -) -from b2sdk._internal.testing.fixtures.buckets import ( # noqa: F401 + +from b2sdk.v3.testing import ( # noqa: F401 pytest_addoption, dont_cleanup_old_buckets, bucket_name_prefix, - bucket_cleaner, + general_bucket_name_prefix, + bucket_manager, bucket, b2_subfolder, + set_http_debug, + b2_auth_data, + _b2_api, + b2_api, ) \ No newline at end of file diff --git a/test/integration/test_download.py b/test/integration/test_download.py index 6c691b4ec..e9252521b 100644 --- a/test/integration/test_download.py +++ b/test/integration/test_download.py @@ -23,8 +23,7 @@ from b2sdk._internal.utils import Sha1HexDigest from b2sdk._internal.utils.filesystem import _IS_WINDOWS from b2sdk.v3 import * -from b2sdk._internal.testing.helpers.base import IntegrationTestBase -from b2sdk._internal.testing.helpers.api import authorize +from b2sdk.v3.testing import IntegrationTestBase, authorize class TestDownload(IntegrationTestBase): diff --git a/test/integration/test_file_version_attributes.py b/test/integration/test_file_version_attributes.py index ed6ffa4a6..bc8863657 100644 --- a/test/integration/test_file_version_attributes.py +++ b/test/integration/test_file_version_attributes.py @@ -11,7 +11,7 @@ import datetime as dt -from b2sdk._internal.testing.helpers.base import IntegrationTestBase +from b2sdk.v3.testing import IntegrationTestBase class TestFileVersionAttributes(IntegrationTestBase): diff --git a/test/integration/test_upload.py b/test/integration/test_upload.py index a47ce4fd6..f3afd921e 100644 --- a/test/integration/test_upload.py +++ b/test/integration/test_upload.py @@ -16,7 +16,7 @@ from b2sdk._internal.encryption.types import EncryptionAlgorithm, EncryptionMode from b2sdk.v2 import B2RawHTTPApi -from b2sdk._internal.testing.helpers.base import IntegrationTestBase +from b2sdk.v3.testing import IntegrationTestBase from .test_raw_api import authorize_raw_api From 0635c77eaaa62a0ee738157e5699b170e5b901c2 Mon Sep 17 00:00:00 2001 From: Kacper Wolkiewicz Date: Sat, 2 Aug 2025 16:16:59 +0200 Subject: [PATCH 3/4] Add licences, changelog and fixes --- b2sdk/_internal/testing/__init__.py | 9 +++++++++ b2sdk/_internal/testing/fixtures/__init__.py | 9 +++++++++ b2sdk/_internal/testing/fixtures/api.py | 14 ++++++++----- b2sdk/_internal/testing/fixtures/buckets.py | 19 ++++++++++-------- b2sdk/_internal/testing/helpers/__init__.py | 9 +++++++++ b2sdk/_internal/testing/helpers/api.py | 12 ++++++----- b2sdk/_internal/testing/helpers/base.py | 6 +++--- .../testing/helpers/bucket_manager.py | 20 ++++++++++--------- b2sdk/_internal/testing/helpers/buckets.py | 4 ++-- b2sdk/v3/testing/__init__.py | 14 ++++++------- changelog.d/+test_helpers.changed.md | 1 + .../integration}/cleanup_buckets.py | 10 ++++------ test/integration/conftest.py | 19 +++++++++--------- test/integration/test_download.py | 4 ++-- test/integration/test_upload.py | 2 +- 15 files changed, 94 insertions(+), 58 deletions(-) create mode 100644 changelog.d/+test_helpers.changed.md rename {b2sdk/_internal/testing => test/integration}/cleanup_buckets.py (63%) diff --git a/b2sdk/_internal/testing/__init__.py b/b2sdk/_internal/testing/__init__.py index e69de29bb..3d2095240 100644 --- a/b2sdk/_internal/testing/__init__.py +++ b/b2sdk/_internal/testing/__init__.py @@ -0,0 +1,9 @@ +###################################################################### +# +# File: b2sdk/_internal/testing/__init__.py +# +# Copyright 2021 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### diff --git a/b2sdk/_internal/testing/fixtures/__init__.py b/b2sdk/_internal/testing/fixtures/__init__.py index e69de29bb..0e8816a33 100644 --- a/b2sdk/_internal/testing/fixtures/__init__.py +++ b/b2sdk/_internal/testing/fixtures/__init__.py @@ -0,0 +1,9 @@ +###################################################################### +# +# File: b2sdk/_internal/testing/fixtures/__init__.py +# +# Copyright 2021 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### diff --git a/b2sdk/_internal/testing/fixtures/api.py b/b2sdk/_internal/testing/fixtures/api.py index b5a11749f..bd1e18c08 100644 --- a/b2sdk/_internal/testing/fixtures/api.py +++ b/b2sdk/_internal/testing/fixtures/api.py @@ -1,7 +1,6 @@ - ###################################################################### # -# File: test/integration/conftest.py +# File: b2sdk/_internal/testing/fixtures/api.py # # Copyright 2021 Backblaze Inc. All Rights Reserved. # @@ -16,7 +15,12 @@ import pytest -from b2sdk._internal.testing.helpers.api import get_b2_auth_data, authorize +from b2sdk._internal.testing.helpers.api import authorize, get_b2_auth_data, get_realm + + +@pytest.fixture(scope='session') +def realm(): + yield get_realm() @pytest.fixture(autouse=True, scope='session') @@ -34,8 +38,8 @@ def b2_auth_data(): @pytest.fixture(scope='session') -def _b2_api(b2_auth_data): - b2_api, _ = authorize(b2_auth_data) +def _b2_api(b2_auth_data, realm): + b2_api, _ = authorize(b2_auth_data, realm) return b2_api diff --git a/b2sdk/_internal/testing/fixtures/buckets.py b/b2sdk/_internal/testing/fixtures/buckets.py index 993200529..11e59231c 100644 --- a/b2sdk/_internal/testing/fixtures/buckets.py +++ b/b2sdk/_internal/testing/fixtures/buckets.py @@ -1,6 +1,6 @@ ###################################################################### # -# File: test/integration/conftest.py +# File: b2sdk/_internal/testing/fixtures/buckets.py # # Copyright 2021 Backblaze Inc. All Rights Reserved. # @@ -8,18 +8,19 @@ # ###################################################################### from __future__ import annotations + import secrets import pytest -from b2sdk._internal.utils import current_time_millis from b2sdk._internal.testing.helpers.bucket_manager import BucketManager from b2sdk._internal.testing.helpers.buckets import ( BUCKET_CREATED_AT_MILLIS, + GENERAL_BUCKET_NAME_PREFIX, get_bucket_name_prefix, random_bucket_name, - GENERAL_BUCKET_NAME_PREFIX, ) +from b2sdk._internal.utils import current_time_millis def pytest_addoption(parser): @@ -47,15 +48,17 @@ def general_bucket_name_prefix(): @pytest.fixture(scope='session') -def bucket_manager(bucket_name_prefix, general_bucket_name_prefix, dont_cleanup_old_buckets, _b2_api): - cleaner = BucketManager( +def bucket_manager( + bucket_name_prefix, general_bucket_name_prefix, dont_cleanup_old_buckets, _b2_api +): + manager = BucketManager( dont_cleanup_old_buckets, _b2_api, current_run_prefix=bucket_name_prefix, - general_prefix=general_bucket_name_prefix + general_prefix=general_bucket_name_prefix, ) - yield cleaner - cleaner.clean_buckets() + yield manager + manager.clean_buckets() @pytest.fixture diff --git a/b2sdk/_internal/testing/helpers/__init__.py b/b2sdk/_internal/testing/helpers/__init__.py index e69de29bb..b2fca191f 100644 --- a/b2sdk/_internal/testing/helpers/__init__.py +++ b/b2sdk/_internal/testing/helpers/__init__.py @@ -0,0 +1,9 @@ +###################################################################### +# +# File: b2sdk/_internal/testing/helpers/__init__.py +# +# Copyright 2021 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### diff --git a/b2sdk/_internal/testing/helpers/api.py b/b2sdk/_internal/testing/helpers/api.py index a02ded4ba..b45adff53 100644 --- a/b2sdk/_internal/testing/helpers/api.py +++ b/b2sdk/_internal/testing/helpers/api.py @@ -1,6 +1,6 @@ ###################################################################### # -# File: test/integration/helpers.py +# File: b2sdk/_internal/testing/helpers/api.py # # Copyright 2022 Backblaze Inc. All Rights Reserved. # @@ -18,6 +18,10 @@ ) +def get_realm() -> str: + return os.environ.get('B2_TEST_ENVIRONMENT', 'production') + + def get_b2_auth_data(): application_key_id = os.environ.get('B2_TEST_APPLICATION_KEY_ID') if application_key_id is None: @@ -29,10 +33,8 @@ def get_b2_auth_data(): return application_key_id, application_key -def authorize(b2_auth_data, api_config=DEFAULT_HTTP_API_CONFIG): +def authorize(b2_auth_data, realm, api_config=DEFAULT_HTTP_API_CONFIG): info = InMemoryAccountInfo() - # TODO ME: Cache? b2_api = B2Api(info, api_config=api_config) - realm = os.environ.get('B2_TEST_ENVIRONMENT', 'production') b2_api.authorize_account(*b2_auth_data, realm=realm) - return b2_api, info \ No newline at end of file + return b2_api, info diff --git a/b2sdk/_internal/testing/helpers/base.py b/b2sdk/_internal/testing/helpers/base.py index b61ec4670..3e5d557fd 100644 --- a/b2sdk/_internal/testing/helpers/base.py +++ b/b2sdk/_internal/testing/helpers/base.py @@ -1,6 +1,6 @@ ###################################################################### # -# File: test/integration/base.py +# File: b2sdk/_internal/testing/helpers/base.py # # Copyright 2022 Backblaze Inc. All Rights Reserved. # @@ -11,13 +11,13 @@ import pytest -from b2sdk.v2 import B2Api, current_time_millis -from b2sdk.v2.exception import DuplicateBucketName from b2sdk._internal.testing.helpers.bucket_manager import BucketManager from b2sdk._internal.testing.helpers.buckets import ( BUCKET_CREATED_AT_MILLIS, random_bucket_name, ) +from b2sdk.v2 import B2Api, current_time_millis +from b2sdk.v2.exception import DuplicateBucketName @pytest.mark.usefixtures('cls_setup') diff --git a/b2sdk/_internal/testing/helpers/bucket_manager.py b/b2sdk/_internal/testing/helpers/bucket_manager.py index 28e00157c..820ce67b3 100644 --- a/b2sdk/_internal/testing/helpers/bucket_manager.py +++ b/b2sdk/_internal/testing/helpers/bucket_manager.py @@ -1,6 +1,6 @@ ###################################################################### # -# File: test/integration/bucket_manager.py +# File: b2sdk/_internal/testing/helpers/bucket_manager.py # # Copyright 2022 Backblaze Inc. All Rights Reserved. # @@ -13,13 +13,18 @@ import platform from collections.abc import Iterable from datetime import datetime, timedelta - from itertools import chain from typing import Any import tenacity -from b2sdk._internal.exception import BucketIdNotFound, TooManyRequests, FileNotPresent +from b2sdk._internal.exception import BucketIdNotFound, FileNotPresent, TooManyRequests +from b2sdk._internal.testing.helpers.buckets import ( + BUCKET_CREATED_AT_MILLIS, + BUCKET_NAME_LENGTH, + GENERAL_BUCKET_NAME_PREFIX, + random_token, +) from b2sdk.v3 import ( NO_RETENTION_FILE_SETTING, B2Api, @@ -29,9 +34,6 @@ current_time_millis, ) from b2sdk.v3.exception import BadRequest -from b2sdk._internal.testing.helpers.buckets import BUCKET_CREATED_AT_MILLIS, GENERAL_BUCKET_NAME_PREFIX, random_token, \ - BUCKET_NAME_LENGTH - NODE_DESCRIPTION = f'{platform.node()}: {platform.platform()}' ONE_HOUR_MILLIS = 60 * 60 * 1000 @@ -45,8 +47,8 @@ def __init__( self, dont_cleanup_old_buckets: bool, b2_api: B2Api, - current_run_prefix: str, - general_prefix: str = GENERAL_BUCKET_NAME_PREFIX + current_run_prefix: str = '', + general_prefix: str = GENERAL_BUCKET_NAME_PREFIX, ): self.current_run_prefix = current_run_prefix self.general_prefix = general_prefix @@ -77,7 +79,7 @@ def create_bucket(self, bucket_type: str = 'allPublic', **kwargs) -> Bucket: ) def _should_remove_bucket(self, bucket: Bucket) -> tuple[bool, str]: - if bucket.name.startswith(self.current_run_prefix): + if self.current_run_prefix and bucket.name.startswith(self.current_run_prefix): return True, 'it is a bucket for this very run' if self.dont_cleanup_old_buckets: return False, 'old buckets ought not to be cleaned' diff --git a/b2sdk/_internal/testing/helpers/buckets.py b/b2sdk/_internal/testing/helpers/buckets.py index f31e1a6b5..5bcef999b 100644 --- a/b2sdk/_internal/testing/helpers/buckets.py +++ b/b2sdk/_internal/testing/helpers/buckets.py @@ -1,6 +1,6 @@ ###################################################################### # -# File: test/integration/helpers.py +# File: b2sdk/_internal/testing/helpers/buckets.py # # Copyright 2022 Backblaze Inc. All Rights Reserved. # @@ -32,4 +32,4 @@ def get_bucket_name_prefix(rnd_len: int = 8) -> str: def random_bucket_name(prefix: str = GENERAL_BUCKET_NAME_PREFIX) -> str: - return prefix + random_token(BUCKET_NAME_LENGTH - len(prefix)) \ No newline at end of file + return prefix + random_token(BUCKET_NAME_LENGTH - len(prefix)) diff --git a/b2sdk/v3/testing/__init__.py b/b2sdk/v3/testing/__init__.py index f3c945388..1899e6ce6 100644 --- a/b2sdk/v3/testing/__init__.py +++ b/b2sdk/v3/testing/__init__.py @@ -11,10 +11,7 @@ # testing - it is not imported in v3.__init__ as it depends on pytest and other test dependencies. -from b2sdk._internal.testing.helpers.api import ( - get_b2_auth_data, - authorize -) +from b2sdk._internal.testing.helpers.api import get_b2_auth_data, authorize, get_realm from b2sdk._internal.testing.helpers.base import IntegrationTestBase from b2sdk._internal.testing.helpers.buckets import ( GENERAL_BUCKET_NAME_PREFIX, @@ -23,19 +20,20 @@ RNG, random_token, get_bucket_name_prefix, - random_bucket_name + random_bucket_name, ) from b2sdk._internal.testing.helpers.bucket_manager import ( NODE_DESCRIPTION, ONE_HOUR_MILLIS, BUCKET_CLEANUP_PERIOD_MILLIS, - BucketManager + BucketManager, ) from b2sdk._internal.testing.fixtures.api import ( set_http_debug, b2_auth_data, _b2_api, - b2_api + b2_api, + realm, ) from b2sdk._internal.testing.fixtures.buckets import ( pytest_addoption, @@ -44,5 +42,5 @@ general_bucket_name_prefix, bucket_manager, bucket, - b2_subfolder + b2_subfolder, ) diff --git a/changelog.d/+test_helpers.changed.md b/changelog.d/+test_helpers.changed.md new file mode 100644 index 000000000..bb386680d --- /dev/null +++ b/changelog.d/+test_helpers.changed.md @@ -0,0 +1 @@ +Improved test helpers and fixtures and moved them to the public API. \ No newline at end of file diff --git a/b2sdk/_internal/testing/cleanup_buckets.py b/test/integration/cleanup_buckets.py similarity index 63% rename from b2sdk/_internal/testing/cleanup_buckets.py rename to test/integration/cleanup_buckets.py index 7bc3fb904..fe1bf56dd 100755 --- a/b2sdk/_internal/testing/cleanup_buckets.py +++ b/test/integration/cleanup_buckets.py @@ -9,14 +9,12 @@ ###################################################################### from __future__ import annotations -from test.integration.helpers import authorize +from b2sdk.v3.testing import BucketManager, authorize, get_b2_auth_data, get_realm -from b2sdk._internal.testing.helpers.api import get_b2_auth_data -from b2sdk._internal.testing.helpers.bucket_manager import BucketManager -# from .test_raw_api import cleanup_old_buckets +from .test_raw_api import cleanup_old_buckets if __name__ == '__main__': - # cleanup_old_buckets() + cleanup_old_buckets() BucketManager( - dont_cleanup_old_buckets=False, b2_api=authorize(get_b2_auth_data())[0] + dont_cleanup_old_buckets=False, b2_api=authorize(get_b2_auth_data(), get_realm())[0] ).clean_buckets() diff --git a/test/integration/conftest.py b/test/integration/conftest.py index 6d430ef16..882086d30 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -9,15 +9,16 @@ ###################################################################### from b2sdk.v3.testing import ( # noqa: F401 - pytest_addoption, - dont_cleanup_old_buckets, + _b2_api, + b2_api, + b2_auth_data, + b2_subfolder, + bucket, + bucket_manager, bucket_name_prefix, + dont_cleanup_old_buckets, general_bucket_name_prefix, - bucket_manager, - bucket, - b2_subfolder, + pytest_addoption, + realm, set_http_debug, - b2_auth_data, - _b2_api, - b2_api, -) \ No newline at end of file +) diff --git a/test/integration/test_download.py b/test/integration/test_download.py index e9252521b..4b3ec1d11 100644 --- a/test/integration/test_download.py +++ b/test/integration/test_download.py @@ -99,7 +99,7 @@ def test_small_unverified(self): @pytest.mark.parametrize('size_multiplier', [1, 100]) -def test_gzip(b2_auth_data, bucket, tmp_path, b2_api, size_multiplier): +def test_gzip(b2_auth_data, bucket, tmp_path, b2_api, size_multiplier, realm): """Test downloading gzipped files of varius sizes with and without content-encoding.""" source_file = tmp_path / 'compressed_file.gz' downloaded_uncompressed_file = tmp_path / 'downloaded_uncompressed_file' @@ -113,7 +113,7 @@ def test_gzip(b2_auth_data, bucket, tmp_path, b2_api, size_multiplier): b2_api.download_file_by_id(file_id=file_version.id_).save_to(str(downloaded_compressed_file)) assert downloaded_compressed_file.read_bytes() == source_file.read_bytes() - decompressing_api, _ = authorize(b2_auth_data, B2HttpApiConfig(decode_content=True)) + decompressing_api, _ = authorize(b2_auth_data, realm, B2HttpApiConfig(decode_content=True)) decompressing_api.download_file_by_id(file_id=file_version.id_).save_to( str(downloaded_uncompressed_file) ) diff --git a/test/integration/test_upload.py b/test/integration/test_upload.py index f3afd921e..8dbebcae2 100644 --- a/test/integration/test_upload.py +++ b/test/integration/test_upload.py @@ -15,8 +15,8 @@ from b2sdk._internal.encryption.setting import EncryptionKey, EncryptionSetting from b2sdk._internal.encryption.types import EncryptionAlgorithm, EncryptionMode from b2sdk.v2 import B2RawHTTPApi - from b2sdk.v3.testing import IntegrationTestBase + from .test_raw_api import authorize_raw_api From 762321a5a106a95ef7b2dd133a6d1b96a395448c Mon Sep 17 00:00:00 2001 From: Kacper Wolkiewicz Date: Thu, 7 Aug 2025 19:56:51 +0200 Subject: [PATCH 4/4] Clean up imports and use bucket manager in base test case. --- b2sdk/_internal/testing/fixtures/buckets.py | 14 ++------------ b2sdk/_internal/testing/helpers/api.py | 8 +++----- b2sdk/_internal/testing/helpers/base.py | 17 +++-------------- .../_internal/testing/helpers/bucket_manager.py | 17 ++++++----------- b2sdk/_internal/testing/helpers/buckets.py | 9 +-------- b2sdk/v3/testing/__init__.py | 1 - test/integration/conftest.py | 16 ++-------------- test/integration/test_raw_api.py | 4 ++-- 8 files changed, 19 insertions(+), 67 deletions(-) diff --git a/b2sdk/_internal/testing/fixtures/buckets.py b/b2sdk/_internal/testing/fixtures/buckets.py index 11e59231c..65da565b4 100644 --- a/b2sdk/_internal/testing/fixtures/buckets.py +++ b/b2sdk/_internal/testing/fixtures/buckets.py @@ -15,12 +15,9 @@ from b2sdk._internal.testing.helpers.bucket_manager import BucketManager from b2sdk._internal.testing.helpers.buckets import ( - BUCKET_CREATED_AT_MILLIS, GENERAL_BUCKET_NAME_PREFIX, get_bucket_name_prefix, - random_bucket_name, ) -from b2sdk._internal.utils import current_time_millis def pytest_addoption(parser): @@ -62,15 +59,8 @@ def bucket_manager( @pytest.fixture -def bucket(b2_api, bucket_name_prefix, bucket_manager): - bucket = b2_api.create_bucket( - random_bucket_name(bucket_name_prefix), - 'allPrivate', - bucket_info={ - 'created_by': 'b2-sdk integration test', - BUCKET_CREATED_AT_MILLIS: str(current_time_millis()), - }, - ) +def bucket(bucket_name_prefix, bucket_manager): + bucket = bucket_manager.create_bucket() yield bucket bucket_manager.clean_bucket(bucket) diff --git a/b2sdk/_internal/testing/helpers/api.py b/b2sdk/_internal/testing/helpers/api.py index b45adff53..8a71875f8 100644 --- a/b2sdk/_internal/testing/helpers/api.py +++ b/b2sdk/_internal/testing/helpers/api.py @@ -11,11 +11,9 @@ import os -from b2sdk.v3 import ( - DEFAULT_HTTP_API_CONFIG, - B2Api, - InMemoryAccountInfo, -) +from b2sdk._internal.account_info.in_memory import InMemoryAccountInfo +from b2sdk._internal.api import B2Api +from b2sdk._internal.api_config import DEFAULT_HTTP_API_CONFIG def get_realm() -> str: diff --git a/b2sdk/_internal/testing/helpers/base.py b/b2sdk/_internal/testing/helpers/base.py index 3e5d557fd..323f542ab 100644 --- a/b2sdk/_internal/testing/helpers/base.py +++ b/b2sdk/_internal/testing/helpers/base.py @@ -12,11 +12,7 @@ import pytest from b2sdk._internal.testing.helpers.bucket_manager import BucketManager -from b2sdk._internal.testing.helpers.buckets import ( - BUCKET_CREATED_AT_MILLIS, - random_bucket_name, -) -from b2sdk.v2 import B2Api, current_time_millis +from b2sdk.v2 import B2Api from b2sdk.v2.exception import DuplicateBucketName @@ -42,9 +38,6 @@ def setup_method(self): for bucket in self.buckets_created: self.bucket_manager.clean_bucket(bucket) - def generate_bucket_name(self): - return random_bucket_name(self.this_run_bucket_name_prefix) - def write_zeros(self, file, number): line = b'0' * 1000 + b'\n' line_len = len(line) @@ -54,13 +47,9 @@ def write_zeros(self, file, number): written += line_len def create_bucket(self): - bucket_name = self.generate_bucket_name() + bucket_name = self.bucket_manager.new_bucket_name() try: - bucket = self.b2_api.create_bucket( - bucket_name, - 'allPrivate', - bucket_info={BUCKET_CREATED_AT_MILLIS: str(current_time_millis())}, - ) + bucket = self.bucket_manager.create_bucket(name=bucket_name) except DuplicateBucketName: self._duplicated_bucket_name_debug_info(bucket_name) raise diff --git a/b2sdk/_internal/testing/helpers/bucket_manager.py b/b2sdk/_internal/testing/helpers/bucket_manager.py index 820ce67b3..2fbbb929c 100644 --- a/b2sdk/_internal/testing/helpers/bucket_manager.py +++ b/b2sdk/_internal/testing/helpers/bucket_manager.py @@ -18,22 +18,17 @@ import tenacity -from b2sdk._internal.exception import BucketIdNotFound, FileNotPresent, TooManyRequests +from b2sdk._internal.api import B2Api +from b2sdk._internal.bucket import Bucket +from b2sdk._internal.exception import BadRequest, BucketIdNotFound, FileNotPresent, TooManyRequests +from b2sdk._internal.file_lock import NO_RETENTION_FILE_SETTING, LegalHold, RetentionMode from b2sdk._internal.testing.helpers.buckets import ( BUCKET_CREATED_AT_MILLIS, BUCKET_NAME_LENGTH, GENERAL_BUCKET_NAME_PREFIX, random_token, ) -from b2sdk.v3 import ( - NO_RETENTION_FILE_SETTING, - B2Api, - Bucket, - LegalHold, - RetentionMode, - current_time_millis, -) -from b2sdk.v3.exception import BadRequest +from b2sdk._internal.utils import current_time_millis NODE_DESCRIPTION = f'{platform.node()}: {platform.platform()}' ONE_HOUR_MILLIS = 60 * 60 * 1000 @@ -70,7 +65,7 @@ def new_bucket_info(self) -> dict: } def create_bucket(self, bucket_type: str = 'allPublic', **kwargs) -> Bucket: - bucket_name = self.new_bucket_name() + bucket_name = kwargs.pop('name', self.new_bucket_name()) return self.b2_api.create_bucket( bucket_name, bucket_type=bucket_type, diff --git a/b2sdk/_internal/testing/helpers/buckets.py b/b2sdk/_internal/testing/helpers/buckets.py index 5bcef999b..a20e1f5f4 100644 --- a/b2sdk/_internal/testing/helpers/buckets.py +++ b/b2sdk/_internal/testing/helpers/buckets.py @@ -11,10 +11,7 @@ import secrets -from b2sdk.v3 import ( - BUCKET_NAME_CHARS_UNIQ, - BUCKET_NAME_LENGTH_RANGE, -) +from b2sdk._internal.http_constants import BUCKET_NAME_CHARS_UNIQ, BUCKET_NAME_LENGTH_RANGE GENERAL_BUCKET_NAME_PREFIX = 'sdktst' BUCKET_NAME_LENGTH = BUCKET_NAME_LENGTH_RANGE[1] @@ -29,7 +26,3 @@ def random_token(length: int, chars: str = BUCKET_NAME_CHARS_UNIQ) -> str: def get_bucket_name_prefix(rnd_len: int = 8) -> str: return GENERAL_BUCKET_NAME_PREFIX + random_token(rnd_len) - - -def random_bucket_name(prefix: str = GENERAL_BUCKET_NAME_PREFIX) -> str: - return prefix + random_token(BUCKET_NAME_LENGTH - len(prefix)) diff --git a/b2sdk/v3/testing/__init__.py b/b2sdk/v3/testing/__init__.py index 1899e6ce6..b0ae2d389 100644 --- a/b2sdk/v3/testing/__init__.py +++ b/b2sdk/v3/testing/__init__.py @@ -20,7 +20,6 @@ RNG, random_token, get_bucket_name_prefix, - random_bucket_name, ) from b2sdk._internal.testing.helpers.bucket_manager import ( NODE_DESCRIPTION, diff --git a/test/integration/conftest.py b/test/integration/conftest.py index 882086d30..ab2b6d380 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -8,17 +8,5 @@ # ###################################################################### -from b2sdk.v3.testing import ( # noqa: F401 - _b2_api, - b2_api, - b2_auth_data, - b2_subfolder, - bucket, - bucket_manager, - bucket_name_prefix, - dont_cleanup_old_buckets, - general_bucket_name_prefix, - pytest_addoption, - realm, - set_http_debug, -) + +pytest_plugins = ["b2sdk.v3.testing"] diff --git a/test/integration/test_raw_api.py b/test/integration/test_raw_api.py index 6306b895f..c60ec55b2 100644 --- a/test/integration/test_raw_api.py +++ b/test/integration/test_raw_api.py @@ -168,7 +168,7 @@ def raw_api_test_helper(raw_api, should_cleanup_old_buckets): account_auth_token, account_id, bucket_name, - 'allPrivate', + 'allPublic', is_file_lock_enabled=True, ) bucket_id = bucket_dict['bucketId'] @@ -208,7 +208,7 @@ def raw_api_test_helper(raw_api, should_cleanup_old_buckets): account_auth_token, account_id, replication_source_bucket_name, - 'allPrivate', + 'allPublic', is_file_lock_enabled=True, replication=ReplicationConfiguration( rules=[