├── b2sdk ├── LICENSE ├── _internal │ ├── requests │ │ ├── README.md │ │ ├── NOTICE │ │ └── included_source_meta.py │ ├── testing │ │ ├── __init__.py │ │ ├── fixtures │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ └── buckets.py │ │ └── helpers │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ ├── base.py │ │ │ └── buckets.py │ ├── scan │ │ ├── __init__.py │ │ └── folder_parser.py │ ├── sync │ │ ├── __init__.py │ │ └── exception.py │ ├── encryption │ │ ├── __init__.py │ │ └── types.py │ ├── large_file │ │ ├── __init__.py │ │ └── part.py │ ├── replication │ │ ├── __init__.py │ │ └── types.py │ ├── transfer │ │ ├── emerge │ │ │ ├── __init__.py │ │ │ ├── planner │ │ │ │ └── __init__.py │ │ │ └── exception.py │ │ ├── inbound │ │ │ ├── __init__.py │ │ │ └── downloader │ │ │ │ └── __init__.py │ │ ├── outbound │ │ │ ├── __init__.py │ │ │ ├── progress_reporter.py │ │ │ ├── outbound_source.py │ │ │ └── large_file_upload_state.py │ │ ├── transfer_manager.py │ │ └── __init__.py │ ├── account_info │ │ ├── __init__.py │ │ └── exception.py │ ├── __init__.py │ ├── stream │ │ ├── base.py │ │ └── __init__.py │ ├── utils │ │ ├── typing.py │ │ ├── filesystem.py │ │ ├── http_date.py │ │ ├── docs.py │ │ └── escape.py │ ├── included_sources.py │ ├── types.py │ ├── http_constants.py │ ├── api_config.py │ └── filter.py ├── __main__.py ├── v1 │ ├── replication │ │ ├── __init__.py │ │ └── monitoring.py │ ├── testing │ │ └── __init__.py │ ├── sync │ │ ├── __init__.py │ │ ├── folder_parser.py │ │ └── report.py │ ├── cache.py │ ├── __init__.py │ ├── exception.py │ ├── b2http.py │ └── file_metadata.py ├── v2 │ ├── replication │ │ └── __init__.py │ ├── sync.py │ ├── testing │ │ └── __init__.py │ ├── _compat.py │ ├── api_config.py │ ├── transfer.py │ ├── b2http.py │ ├── exception.py │ ├── utils.py │ ├── large_file.py │ ├── version_utils.py │ └── __init__.py ├── v0 │ ├── testing │ │ └── __init__.py │ ├── __init__.py │ ├── exception.py │ ├── api.py │ ├── bucket.py │ └── account_info.py ├── __init__.py ├── _pyinstaller │ ├── hook-b2sdk.py │ └── __init__.py ├── version.py └── v3 │ └── testing │ └── __init__.py ├── changelog.d ├── .gitkeep └── +pytest_plugins-toplevel-conftest.infrastructure.md ├── setup.cfg ├── doc ├── render_sqlite_account_info_schema.sh ├── source │ ├── api │ │ ├── bucket.rst │ │ ├── encryption │ │ │ ├── types.rst │ │ │ └── setting.rst │ │ ├── downloaded_file.rst │ │ ├── transfer │ │ │ ├── emerge │ │ │ │ └── write_intent.rst │ │ │ └── outbound │ │ │ │ └── outbound_source.rst │ │ ├── internal │ │ │ ├── b2http.rst │ │ │ ├── requests.rst │ │ │ ├── cache.rst │ │ │ ├── utils.rst │ │ │ ├── scan │ │ │ │ ├── path.rst │ │ │ │ ├── scan.rst │ │ │ │ ├── folder.rst │ │ │ │ ├── policies.rst │ │ │ │ └── folder_parser.rst │ │ │ ├── sync │ │ │ │ ├── sync.rst │ │ │ │ ├── action.rst │ │ │ │ ├── policy.rst │ │ │ │ ├── exception.rst │ │ │ │ └── policy_manager.rst │ │ │ ├── session.rst │ │ │ ├── raw_api.rst │ │ │ ├── stream │ │ │ │ ├── chained.rst │ │ │ │ ├── hashing.rst │ │ │ │ ├── range.rst │ │ │ │ ├── wrapper.rst │ │ │ │ └── progress.rst │ │ │ ├── raw_simulator.rst │ │ │ └── transfer │ │ │ │ ├── outbound │ │ │ │ └── upload_source.rst │ │ │ │ └── inbound │ │ │ │ ├── downloader │ │ │ │ ├── simple.rst │ │ │ │ ├── parallel.rst │ │ │ │ └── abstract.rst │ │ │ │ └── download_manager.rst │ │ ├── exception.rst │ │ ├── api.rst │ │ ├── application_key.rst │ │ ├── enums.rst │ │ ├── utils.rst │ │ ├── data_classes.rst │ │ ├── cache.rst │ │ ├── progress.rst │ │ └── file_lock.rst │ ├── install.rst │ ├── api_reference.rst │ ├── glossary.rst │ └── index.rst ├── bash_completion.md └── sqlite_account_info_schema.py ├── README.release.md ├── .gitignore ├── test ├── unit │ ├── stream │ │ ├── __init__.py │ │ └── test_progress.py │ ├── internal │ │ ├── transfer │ │ │ ├── __init__.py │ │ │ └── downloader │ │ │ │ └── __init__.py │ │ └── __init__.py │ ├── __init__.py │ ├── api │ │ └── __init__.py │ ├── v0 │ │ ├── __init__.py │ │ ├── apiver │ │ │ ├── apiver_deps.py │ │ │ ├── apiver_deps_exception.py │ │ │ └── __init__.py │ │ ├── deps.py │ │ ├── deps_exception.py │ │ ├── test_file_metadata.py │ │ ├── test_progress.py │ │ ├── test_bounded_queue_executor.py │ │ └── test_scan_policies.py │ ├── v1 │ │ ├── __init__.py │ │ ├── apiver │ │ │ ├── apiver_deps.py │ │ │ ├── apiver_deps_exception.py │ │ │ └── __init__.py │ │ ├── deps.py │ │ ├── deps_exception.py │ │ ├── test_file_metadata.py │ │ ├── test_progress.py │ │ ├── test_bounded_queue_executor.py │ │ └── test_scan_policies.py │ ├── v2 │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── apiver │ │ │ ├── apiver_deps.py │ │ │ ├── apiver_deps_exception.py │ │ │ └── __init__.py │ │ ├── test_utils.py │ │ ├── test_bucket.py │ │ └── test_raw_api.py │ ├── v3 │ │ ├── __init__.py │ │ └── apiver │ │ │ ├── apiver_deps.py │ │ │ ├── apiver_deps_exception.py │ │ │ └── __init__.py │ ├── scan │ │ ├── __init__.py │ │ └── test_folder.py │ ├── sync │ │ ├── __init__.py │ │ ├── test_sync_report.py │ │ └── fixtures.py │ ├── utils │ │ ├── __init__.py │ │ ├── test_thread_pool.py │ │ ├── test_docs.py │ │ ├── test_filesystem.py │ │ ├── test_range_.py │ │ └── test_escape.py │ ├── v_all │ │ ├── __init__.py │ │ ├── test_constants.py │ │ └── test_transfer.py │ ├── b2http │ │ └── __init__.py │ ├── bucket │ │ └── __init__.py │ ├── filter │ │ ├── __init__.py │ │ └── test_filter.py │ ├── account_info │ │ └── __init__.py │ ├── file_version │ │ └── __init__.py │ ├── fixtures │ │ ├── __init__.py │ │ ├── b2http.py │ │ ├── cache.py │ │ ├── session.py │ │ └── raw_api.py │ ├── test_included_modules.py │ ├── test_progress.py │ └── replication │ │ └── conftest.py ├── __init__.py ├── static │ ├── __init__.py │ └── test_licenses.py ├── integration │ ├── __init__.py │ ├── cleanup_buckets.py │ ├── test_sync.py │ ├── test_bucket.py │ └── test_file_version_attributes.py └── conftest.py ├── .github ├── dependabot.yml ├── SUPPORT.md ├── no-response.yml └── workflows │ └── cd.yml ├── contrib ├── color-b2-logs.sh └── debug_logs.ini ├── .readthedocs.yml └── README.md /b2sdk/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /changelog.d/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [coverage:run] 2 | branch=true 3 | -------------------------------------------------------------------------------- /changelog.d/+pytest_plugins-toplevel-conftest.infrastructure.md: -------------------------------------------------------------------------------- 1 | Move pytest_plugins to top-level conftest (fix for newer pytest). -------------------------------------------------------------------------------- /doc/render_sqlite_account_info_schema.sh: -------------------------------------------------------------------------------- 1 | python ../sqlite_account_info_schema.py > source/dot/sqlite_account_info_schema.dot 2 | -------------------------------------------------------------------------------- /doc/source/api/bucket.rst: -------------------------------------------------------------------------------- 1 | B2 Bucket 2 | ========= 3 | 4 | .. autoclass:: b2sdk.v3.Bucket() 5 | :inherited-members: 6 | :special-members: __init__ 7 | -------------------------------------------------------------------------------- /README.release.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | - Run `nox -s make_release_commit -- X.Y.Z` where `X.Y.Z` is the version you're releasing, and follow the instructions 4 | -------------------------------------------------------------------------------- /doc/source/api/encryption/types.rst: -------------------------------------------------------------------------------- 1 | .. _encryption_types: 2 | 3 | Encryption Types 4 | ================ 5 | 6 | .. automodule:: b2sdk._internal.encryption.types 7 | -------------------------------------------------------------------------------- /doc/source/api/downloaded_file.rst: -------------------------------------------------------------------------------- 1 | Downloaded File 2 | =============== 3 | 4 | .. autoclass:: b2sdk.v3.DownloadedFile 5 | 6 | .. autoclass:: b2sdk.v3.MtimeUpdatedFile 7 | -------------------------------------------------------------------------------- /doc/source/api/transfer/emerge/write_intent.rst: -------------------------------------------------------------------------------- 1 | Write intent 2 | ============ 3 | 4 | .. autoclass:: b2sdk.v3.WriteIntent() 5 | :inherited-members: 6 | :special-members: __init__ 7 | -------------------------------------------------------------------------------- /doc/source/api/internal/b2http.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.b2http` -- thin http client wrapper 2 | ========================================================= 3 | 4 | .. automodule:: b2sdk._internal.b2http 5 | -------------------------------------------------------------------------------- /doc/source/api/transfer/outbound/outbound_source.rst: -------------------------------------------------------------------------------- 1 | Outbound Transfer Source 2 | ======================== 3 | 4 | .. autoclass:: b2sdk.v3.OutboundTransferSource() 5 | :inherited-members: 6 | :special-members: __init__ 7 | -------------------------------------------------------------------------------- /doc/source/api/exception.rst: -------------------------------------------------------------------------------- 1 | Exceptions 2 | ========== 3 | 4 | .. todo:: 5 | improve documentation of exceptions, automodule -> autoclass? 6 | 7 | .. automodule:: b2sdk.v3.exception 8 | :members: 9 | :undoc-members: 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .codacy-coverage/ 3 | .coverage 4 | .eggs/ 5 | .idea 6 | .nox/ 7 | .python-version 8 | b2sdk.egg-info 9 | build 10 | coverage.xml 11 | dist 12 | venv 13 | .venv 14 | .vscode 15 | .pdm-build/ 16 | .pdm-python 17 | .env -------------------------------------------------------------------------------- /doc/source/api/internal/requests.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.requests` -- modified requests.models.Response class 2 | ========================================================================== 3 | 4 | .. automodule:: b2sdk._internal.requests 5 | -------------------------------------------------------------------------------- /doc/source/api/internal/cache.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.cache` 2 | ============================ 3 | 4 | .. automodule:: b2sdk._internal.cache 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/utils.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.utils` 2 | ============================ 3 | 4 | .. automodule:: b2sdk._internal.utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/scan/path.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.scan.path` 2 | ================================ 3 | 4 | .. automodule:: b2sdk._internal.scan.path 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/scan/scan.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.scan.scan` 2 | ================================ 3 | 4 | .. automodule:: b2sdk._internal.scan.scan 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/sync/sync.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.sync.sync` 2 | ================================ 3 | 4 | .. automodule:: b2sdk._internal.sync.sync 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /b2sdk/_internal/requests/README.md: -------------------------------------------------------------------------------- 1 | This module contains modified parts of the requests module (https://github.com/psf/requests). 2 | The modules original license is included in LICENSE. 3 | Changes made to the original source are listed in NOTICE, along with original NOTICE. -------------------------------------------------------------------------------- /doc/source/api/internal/scan/folder.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.scan.folder` 2 | ================================== 3 | 4 | .. automodule:: b2sdk._internal.scan.folder 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/sync/action.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.sync.action` 2 | ================================== 3 | 4 | .. automodule:: b2sdk._internal.sync.action 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/sync/policy.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.sync.policy` 2 | ================================== 3 | 4 | .. automodule:: b2sdk._internal.sync.policy 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/scan/policies.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.scan.policies` 2 | ==================================== 3 | 4 | .. automodule:: b2sdk._internal.scan.policies 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/session.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.session` -- B2 Session 2 | ============================================ 3 | 4 | .. automodule:: b2sdk._internal.session 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/sync/exception.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.sync.exception` 2 | ===================================== 3 | 4 | .. automodule:: b2sdk._internal.sync.exception 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/api.rst: -------------------------------------------------------------------------------- 1 | B2 Api client 2 | ============= 3 | 4 | .. autoclass:: b2sdk.v3.B2Api() 5 | :inherited-members: 6 | :special-members: __init__ 7 | 8 | 9 | .. autoclass:: b2sdk.v3.B2HttpApiConfig() 10 | :inherited-members: 11 | :special-members: __init__ 12 | -------------------------------------------------------------------------------- /doc/source/api/internal/raw_api.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.raw_api` -- B2 raw api wrapper 2 | ==================================================== 3 | 4 | .. automodule:: b2sdk._internal.raw_api 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/scan/folder_parser.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.scan.folder_parser` 2 | ========================================= 3 | 4 | .. automodule:: b2sdk._internal.scan.folder_parser 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/sync/policy_manager.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.sync.policy_manager` 2 | ========================================== 3 | 4 | .. automodule:: b2sdk._internal.sync.policy_manager 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/stream/chained.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.stream.chained` ChainedStream 2 | =================================================== 3 | 4 | .. automodule:: b2sdk._internal.stream.chained 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/stream/hashing.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.stream.hashing` StreamWithHash 2 | ==================================================== 3 | 4 | .. automodule:: b2sdk._internal.stream.hashing 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/stream/range.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.stream.range` RangeOfInputStream 2 | ====================================================== 3 | 4 | .. automodule:: b2sdk._internal.stream.range 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/stream/wrapper.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.stream.wrapper` StreamWrapper 2 | =================================================== 3 | 4 | .. automodule:: b2sdk._internal.stream.wrapper 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/application_key.rst: -------------------------------------------------------------------------------- 1 | B2 Application key 2 | ================== 3 | 4 | .. autoclass:: b2sdk.v3.ApplicationKey() 5 | :inherited-members: 6 | :special-members: __init__ 7 | 8 | 9 | .. autoclass:: b2sdk.v3.FullApplicationKey() 10 | :inherited-members: 11 | :special-members: __init__ 12 | -------------------------------------------------------------------------------- /doc/source/api/internal/raw_simulator.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.raw_simulator` -- B2 raw api simulator 2 | ============================================================ 3 | 4 | .. automodule:: b2sdk._internal.raw_simulator 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /b2sdk/_internal/requests/NOTICE: -------------------------------------------------------------------------------- 1 | Requests 2 | Copyright 2019 Kenneth Reitz 3 | 4 | Copyright 2021 Backblaze Inc. 5 | Changes made to the original source: 6 | requests.models.Response.iter_content has been overridden to pass `decode_content=False` argument to `self.raw.stream` 7 | in order to NOT decompress data based on Content-Encoding header -------------------------------------------------------------------------------- /doc/source/api/internal/stream/progress.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.stream.progress` Streams with progress reporting 2 | ====================================================================== 3 | 4 | .. automodule:: b2sdk._internal.stream.progress 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/transfer/outbound/upload_source.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.transfer.outbound.upload_source` 2 | ====================================================== 3 | 4 | .. automodule:: b2sdk._internal.transfer.outbound.upload_source 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /test/unit/stream/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/stream/__init__.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | -------------------------------------------------------------------------------- /doc/source/api/enums.rst: -------------------------------------------------------------------------------- 1 | Enums 2 | ===== 3 | 4 | .. autoclass:: b2sdk.v3.MetadataDirectiveMode 5 | :inherited-members: 6 | 7 | .. autoclass:: b2sdk.v3.NewerFileSyncMode 8 | :inherited-members: 9 | 10 | .. autoclass:: b2sdk.v3.CompareVersionMode 11 | :inherited-members: 12 | 13 | .. autoclass:: b2sdk.v3.KeepOrDeleteMode 14 | :inherited-members: 15 | -------------------------------------------------------------------------------- /b2sdk/_internal/testing/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/testing/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | -------------------------------------------------------------------------------- /b2sdk/__main__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/__main__.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/internal/transfer/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/internal/transfer/__init__.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Documentation available at 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "pip" 7 | directory: "/" 8 | schedule: 9 | interval: "daily" 10 | # This setting does not affect security updates 11 | open-pull-requests-limit: 0 -------------------------------------------------------------------------------- /test/unit/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /b2sdk/_internal/testing/fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/testing/fixtures/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | -------------------------------------------------------------------------------- /b2sdk/_internal/testing/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/testing/helpers/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | -------------------------------------------------------------------------------- /test/static/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/static/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/api/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/api/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/v0/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v0/__init__.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/v1/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v1/__init__.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/v2/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v2/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/v2/conftest.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v2/conftest.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/v3/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v3/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /doc/source/api/internal/transfer/inbound/downloader/simple.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.transfer.inbound.downloader.simple` -- SimpleDownloader 2 | ============================================================================= 3 | 4 | .. automodule:: b2sdk._internal.transfer.inbound.downloader.simple 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /test/unit/scan/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/scan/__init__.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/sync/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/sync/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/utils/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/utils/__init__.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/v_all/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v_all/__init__.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/integration/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/integration/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/b2http/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/b2http/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/bucket/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/bucket/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/filter/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/filter/__init__.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/internal/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/internal/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/internal/transfer/downloader/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/internal/transfer/downloader/__init__.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | -------------------------------------------------------------------------------- /b2sdk/_internal/scan/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/scan/__init__.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /b2sdk/_internal/sync/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/sync/__init__.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /b2sdk/v1/replication/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v1/replication/__init__.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /b2sdk/v2/replication/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v2/replication/__init__.py 4 | # 5 | # Copyright 2025 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /doc/source/api/internal/transfer/inbound/download_manager.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.transfer.inbound.download_manager` -- Manager of downloaders 2 | ================================================================================== 3 | 4 | .. automodule:: b2sdk._internal.transfer.inbound.download_manager 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /doc/source/api/internal/transfer/inbound/downloader/parallel.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.transfer.inbound.downloader.parallel` -- ParallelTransferer 2 | ================================================================================= 3 | 4 | .. automodule:: b2sdk._internal.transfer.inbound.downloader.parallel 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /test/unit/account_info/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/account_info/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /test/unit/file_version/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/file_version/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /doc/source/api/internal/transfer/inbound/downloader/abstract.rst: -------------------------------------------------------------------------------- 1 | :mod:`b2sdk._internal.transfer.inbound.downloader.abstract` -- Downloader base class 2 | ==================================================================================== 3 | 4 | .. automodule:: b2sdk._internal.transfer.inbound.downloader.abstract 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | -------------------------------------------------------------------------------- /b2sdk/_internal/encryption/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/encryption/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /b2sdk/_internal/large_file/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/large_file/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /b2sdk/_internal/replication/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/replication/__init__.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /b2sdk/_internal/transfer/emerge/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/transfer/emerge/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /b2sdk/_internal/transfer/inbound/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/transfer/inbound/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /b2sdk/_internal/transfer/outbound/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/transfer/outbound/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /b2sdk/_internal/transfer/emerge/planner/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/transfer/emerge/planner/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /b2sdk/v2/sync.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v2/sync.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v3 import B2Path 13 | 14 | B2SyncPath = B2Path 15 | -------------------------------------------------------------------------------- /b2sdk/_internal/transfer/inbound/downloader/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/transfer/inbound/downloader/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | -------------------------------------------------------------------------------- /b2sdk/v0/testing/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v0/testing/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v1.testing import * # noqa 13 | -------------------------------------------------------------------------------- /b2sdk/v1/testing/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v1/testing/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v2.testing import * # noqa 13 | -------------------------------------------------------------------------------- /b2sdk/v2/testing/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v2/testing/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v3.testing import * # noqa 13 | -------------------------------------------------------------------------------- /test/unit/v0/apiver/apiver_deps.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v0/apiver/apiver_deps.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v0 import * # noqa 13 | 14 | V = 0 15 | -------------------------------------------------------------------------------- /test/unit/v1/apiver/apiver_deps.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v1/apiver/apiver_deps.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v1 import * # noqa 13 | 14 | V = 1 15 | -------------------------------------------------------------------------------- /test/unit/v2/apiver/apiver_deps.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v2/apiver/apiver_deps.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v2 import * # noqa 13 | 14 | V = 2 15 | -------------------------------------------------------------------------------- /test/unit/v3/apiver/apiver_deps.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v3/apiver/apiver_deps.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v3 import * # noqa 13 | 14 | V = 3 15 | -------------------------------------------------------------------------------- /test/unit/v0/apiver/apiver_deps_exception.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v0/apiver/apiver_deps_exception.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v0.exception import * # noqa 13 | -------------------------------------------------------------------------------- /test/unit/v1/apiver/apiver_deps_exception.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v1/apiver/apiver_deps_exception.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v1.exception import * # noqa 13 | -------------------------------------------------------------------------------- /test/unit/v2/apiver/apiver_deps_exception.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v2/apiver/apiver_deps_exception.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v2.exception import * # noqa 13 | -------------------------------------------------------------------------------- /test/unit/v3/apiver/apiver_deps_exception.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v3/apiver/apiver_deps_exception.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v3.exception import * # noqa 13 | -------------------------------------------------------------------------------- /doc/source/api/utils.rst: -------------------------------------------------------------------------------- 1 | B2 Utility functions 2 | ==================== 3 | 4 | .. autofunction:: b2sdk.v3.b2_url_encode 5 | .. autofunction:: b2sdk.v3.b2_url_decode 6 | .. autofunction:: b2sdk.v3.choose_part_ranges 7 | .. autofunction:: b2sdk.v3.fix_windows_path_limit 8 | .. autofunction:: b2sdk.v3.format_and_scale_fraction 9 | .. autofunction:: b2sdk.v3.format_and_scale_number 10 | .. autofunction:: b2sdk.v3.hex_sha1_of_stream 11 | .. autofunction:: b2sdk.v3.hex_sha1_of_bytes 12 | -------------------------------------------------------------------------------- /b2sdk/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/__init__.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import b2sdk.version # noqa: E402 13 | 14 | __version__ = b2sdk.version.VERSION 15 | assert __version__ # PEP-0396 16 | -------------------------------------------------------------------------------- /b2sdk/_pyinstaller/hook-b2sdk.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_pyinstaller/hook-b2sdk.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from PyInstaller.utils.hooks import copy_metadata 13 | 14 | datas = copy_metadata('b2sdk') 15 | -------------------------------------------------------------------------------- /b2sdk/_internal/account_info/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/account_info/__init__.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from .in_memory import InMemoryAccountInfo 13 | 14 | assert InMemoryAccountInfo 15 | -------------------------------------------------------------------------------- /test/unit/fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/fixtures/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from .b2http import * 13 | from .cache import * 14 | from .raw_api import * 15 | from .session import * 16 | -------------------------------------------------------------------------------- /b2sdk/_internal/sync/exception.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/sync/exception.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from ..exception import B2SimpleError 13 | 14 | 15 | class IncompleteSync(B2SimpleError): 16 | pass 17 | -------------------------------------------------------------------------------- /test/unit/v0/apiver/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v0/apiver/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | # configured by pytest using `--api` option 13 | # check test/unit/conftest.py:pytest_configure for details 14 | -------------------------------------------------------------------------------- /test/unit/v1/apiver/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v1/apiver/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | # configured by pytest using `--api` option 13 | # check test/unit/conftest.py:pytest_configure for details 14 | -------------------------------------------------------------------------------- /test/unit/v2/apiver/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v2/apiver/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | # configured by pytest using `--api` option 13 | # check test/unit/conftest.py:pytest_configure for details 14 | -------------------------------------------------------------------------------- /test/unit/v3/apiver/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v3/apiver/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | # configured by pytest using `--api` option 13 | # check test/unit/conftest.py:pytest_configure for details 14 | -------------------------------------------------------------------------------- /b2sdk/_internal/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/__init__.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | """ 11 | b2sdk._internal package contains internal modules, and should not be used directly. 12 | 13 | Please use chosen apiver package instead, e.g. b2sdk.v3 14 | """ 15 | -------------------------------------------------------------------------------- /b2sdk/v2/_compat.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v2/_compat.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | from b2sdk._internal import version_utils 12 | 13 | _file_infos_rename = version_utils.rename_argument('file_infos', 'file_info', None, 'v3') 14 | -------------------------------------------------------------------------------- /contrib/color-b2-logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | awk -F '\t' '{print $1 " " $4 " " $5 " " $6}' | colorex --green=DEBUG \ 3 | --bgreen=INFO \ 4 | --bred=ERROR \ 5 | --byellow=WARNING \ 6 | --bmagenta='calling [\w\.]+' \ 7 | --bblue='INFO // =+ [0-9\.]+ =+ \\' \ 8 | --bblue='INFO // =+ [0-9\.]+ =+ \\' \ 9 | --bblue='starting command .* with arguments:' \ 10 | --bblue='starting command .* \(arguments hidden\)' \ 11 | --red=Traceback \ 12 | --green='\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d,\d\d\d' \ 13 | --cyan='b2\.sync' 14 | -------------------------------------------------------------------------------- /b2sdk/v2/api_config.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v2/api_config.py 4 | # 5 | # Copyright 2025 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from b2sdk import v3 11 | from .raw_api import B2RawHTTPApi 12 | 13 | 14 | class B2HttpApiConfig(v3.B2HttpApiConfig): 15 | DEFAULT_RAW_API_CLASS = B2RawHTTPApi 16 | 17 | 18 | DEFAULT_HTTP_API_CONFIG = B2HttpApiConfig() 19 | -------------------------------------------------------------------------------- /test/unit/fixtures/b2http.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/fixtures/b2http.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import pytest 13 | from apiver_deps import B2Http 14 | 15 | 16 | @pytest.fixture 17 | def fake_b2http(mocker): 18 | return mocker.MagicMock(name='FakeB2Http', spec=B2Http) 19 | -------------------------------------------------------------------------------- /doc/source/api/data_classes.rst: -------------------------------------------------------------------------------- 1 | Data classes 2 | ============ 3 | 4 | .. autoclass:: b2sdk.v3.FileVersion 5 | :inherited-members: 6 | :special-members: __dict__ 7 | 8 | .. autoclass:: b2sdk.v3.DownloadVersion 9 | :inherited-members: 10 | 11 | .. autoclass:: b2sdk.v3.FileIdAndName 12 | :inherited-members: 13 | :special-members: __dict__ 14 | 15 | .. autoclass:: b2sdk.v3.UnfinishedLargeFile() 16 | :no-members: 17 | 18 | .. autoclass:: b2sdk.v3.Part 19 | :no-members: 20 | 21 | .. autoclass:: b2sdk.v3.Range 22 | :no-members: 23 | :special-members: __init__ 24 | -------------------------------------------------------------------------------- /test/unit/fixtures/cache.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/fixtures/cache.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import pytest 13 | from apiver_deps import InMemoryCache 14 | 15 | 16 | @pytest.fixture 17 | def fake_cache(mocker): 18 | return mocker.MagicMock(name='FakeCache', spec=InMemoryCache) 19 | -------------------------------------------------------------------------------- /b2sdk/v1/sync/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v1/sync/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from .encryption_provider import * 13 | from .file import * 14 | from .folder import * 15 | from .folder_parser import * 16 | from .report import * 17 | from .scan_policies import * 18 | from .sync import * 19 | -------------------------------------------------------------------------------- /doc/source/api/encryption/setting.rst: -------------------------------------------------------------------------------- 1 | .. _encryption_setting: 2 | 3 | Encryption Settings 4 | =================== 5 | 6 | .. autoclass:: b2sdk.v3.EncryptionKey() 7 | :no-members: 8 | :special-members: __init__ 9 | 10 | .. autoclass:: b2sdk.v3.UNKNOWN_KEY_ID 11 | :no-members: 12 | 13 | .. autoclass:: b2sdk.v3.EncryptionSetting() 14 | :no-members: 15 | :special-members: __init__, as_dict 16 | 17 | 18 | .. autoattribute:: b2sdk.v3.SSE_NONE 19 | 20 | Commonly used "no encryption" setting 21 | 22 | 23 | .. autoattribute:: b2sdk.v3.SSE_B2_AES 24 | 25 | Commonly used SSE-B2 setting 26 | -------------------------------------------------------------------------------- /b2sdk/v1/cache.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v1/cache.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk import v2 13 | 14 | 15 | class AbstractCache(v2.AbstractCache): 16 | def get_bucket_name_or_none_from_bucket_id(self, bucket_id: str) -> str | None: 17 | return None 18 | # Removed @abstractmethod decorator 19 | -------------------------------------------------------------------------------- /doc/source/api/cache.rst: -------------------------------------------------------------------------------- 1 | Cache 2 | ===== 3 | 4 | **b2sdk** caches the mapping between bucket name and bucket 5 | id, so that the user of the library does not need to maintain 6 | the mapping to call the api. 7 | 8 | 9 | .. autoclass:: b2sdk.v3.AbstractCache 10 | :inherited-members: 11 | 12 | .. autoclass:: b2sdk.v3.AuthInfoCache() 13 | :inherited-members: 14 | :special-members: __init__ 15 | 16 | .. autoclass:: b2sdk.v3.DummyCache() 17 | :inherited-members: 18 | :special-members: __init__ 19 | 20 | .. autoclass:: b2sdk.v3.InMemoryCache() 21 | :inherited-members: 22 | :special-members: __init__ 23 | -------------------------------------------------------------------------------- /b2sdk/_internal/stream/base.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/stream/base.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import io 13 | 14 | 15 | class ReadOnlyStreamMixin: 16 | def writeable(self): 17 | return False 18 | 19 | def write(self, data): 20 | raise io.UnsupportedOperation('Cannot accept a write to a read-only stream') 21 | -------------------------------------------------------------------------------- /b2sdk/_internal/utils/typing.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/utils/typing.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from typing import Dict, List, Union 13 | 14 | try: 15 | from typing_extensions import TypeAlias 16 | except ImportError: 17 | from typing import TypeAlias 18 | 19 | JSON: TypeAlias = Union[Dict[str, 'JSON'], List['JSON'], str, int, float, bool, None] 20 | -------------------------------------------------------------------------------- /b2sdk/_pyinstaller/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_pyinstaller/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import os 13 | 14 | 15 | def get_hook_dirs(): 16 | """Get hooks directories for pyinstaller. 17 | 18 | More info about the hooks: 19 | https://pyinstaller.readthedocs.io/en/stable/hooks.html 20 | """ 21 | return [os.path.dirname(__file__)] 22 | -------------------------------------------------------------------------------- /test/unit/v2/test_utils.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v2/test_utils.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | import os 11 | import os.path 12 | 13 | import pytest 14 | 15 | from .apiver.apiver_deps import TempDir 16 | 17 | 18 | def test_temp_dir() -> None: 19 | with pytest.deprecated_call(): 20 | with TempDir() as temp_dir: 21 | assert os.path.exists(temp_dir) 22 | assert not os.path.exists(temp_dir) 23 | -------------------------------------------------------------------------------- /b2sdk/_internal/transfer/emerge/exception.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/transfer/emerge/exception.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk._internal.exception import B2SimpleError 13 | 14 | 15 | class UnboundStreamBufferTimeout(B2SimpleError): 16 | """ 17 | Raised when there is no space for a new buffer for a certain amount of time. 18 | """ 19 | 20 | pass 21 | -------------------------------------------------------------------------------- /b2sdk/_internal/transfer/transfer_manager.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/transfer/transfer_manager.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | 13 | class TransferManager: 14 | """ 15 | Base class for manager classes (copy, upload, download) 16 | """ 17 | 18 | def __init__(self, services, **kwargs): 19 | self.services = services 20 | super().__init__(**kwargs) 21 | -------------------------------------------------------------------------------- /b2sdk/v2/transfer.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v2/transfer.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk import v3 13 | from b2sdk._internal.utils.thread_pool import LazyThreadPool # noqa: F401 14 | 15 | 16 | class ThreadPoolMixin(v3.ThreadPoolMixin): 17 | pass 18 | 19 | 20 | class DownloadManager(v3.DownloadManager): 21 | pass 22 | 23 | 24 | class UploadManager(v3.UploadManager): 25 | pass 26 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | Issues with **B2_Command_line_tool** (problems with the tool itself) should be reported at [B2 CLI issue tracker](https://github.com/Backblaze/B2_Command_Line_Tool/issues). 2 | 3 | Issuew with **B2 cloud service** (not caused by B2 CLI or sdk) should be reported directly to [Backblaze support](https://help.backblaze.com/hc/en-us/requests/new). 4 | 5 | Issues with the B2 python sdk should be reported in [b2-sdk-python issue tracker](https://github.com/Backblaze/b2-sdk-python/issues). This should be used by authors of tools that interact with the b2 cloud through **b2-sdk-python**, but also if an issue with some other project (that uses **b2-sdk-python**) is clearly caused by a bug in **b2-sdk-python**. 6 | -------------------------------------------------------------------------------- /test/unit/v0/deps.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v0/deps.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | # TODO: This module is used in old-style unit tests, written separately for v0 and v1. 13 | # It will be removed when all test are rewritten for the new style, like e.g. test/unit/sync/. 14 | # configured by pytest using `--api` option 15 | # check test/unit/conftest.py:pytest_configure for details 16 | from apiver_deps import * 17 | -------------------------------------------------------------------------------- /test/unit/v1/deps.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v1/deps.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | # TODO: This module is used in old-style unit tests, written separately for v0 and v1. 13 | # It will be removed when all test are rewritten for the new style, like e.g. test/unit/sync/. 14 | # configured by pytest using `--api` option 15 | # check test/unit/conftest.py:pytest_configure for details 16 | from apiver_deps import * 17 | -------------------------------------------------------------------------------- /b2sdk/_internal/stream/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/stream/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from .hashing import StreamWithHash 13 | from .progress import ReadingStreamWithProgress, WritingStreamWithProgress 14 | from .range import RangeOfInputStream 15 | 16 | __all__ = [ 17 | 'RangeOfInputStream', 18 | 'ReadingStreamWithProgress', 19 | 'StreamWithHash', 20 | 'WritingStreamWithProgress', 21 | ] 22 | -------------------------------------------------------------------------------- /doc/source/install.rst: -------------------------------------------------------------------------------- 1 | ######################## 2 | Installation Guide 3 | ######################## 4 | 5 | Installing as a dependency 6 | ========================== 7 | 8 | **b2sdk** can simply be added to ``requirements.txt`` (or equivalent such as ``setup.py``, ``.pipfile`` etc). 9 | In order to properly set a dependency, see :ref:`versioning chapter ` for details. 10 | 11 | .. note:: 12 | The stability of your application depends on correct :ref:`pinning of versions `. 13 | 14 | 15 | Installing a development version 16 | ================================ 17 | 18 | To install **b2sdk**, checkout the repository and run:: 19 | 20 | pip install b2sdk 21 | 22 | in your python environment. 23 | -------------------------------------------------------------------------------- /b2sdk/_internal/transfer/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/transfer/__init__.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from .inbound.download_manager import DownloadManager 13 | from .outbound.copy_manager import CopyManager 14 | from .outbound.upload_manager import UploadManager 15 | from .emerge.emerger import Emerger 16 | 17 | __all__ = [ 18 | 'DownloadManager', 19 | 'CopyManager', 20 | 'UploadManager', 21 | 'Emerger', 22 | ] 23 | -------------------------------------------------------------------------------- /test/unit/v0/deps_exception.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v0/deps_exception.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | # TODO: This module is used in old-style unit tests, written separately for v0 and v1. 13 | # It will be removed when all test are rewritten for the new style, like e.g. test/unit/sync/. 14 | # configured by pytest using `--api` option 15 | # check test/unit/conftest.py:pytest_configure for details 16 | from apiver_deps_exception import * 17 | -------------------------------------------------------------------------------- /test/unit/v1/deps_exception.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v1/deps_exception.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | # TODO: This module is used in old-style unit tests, written separately for v0 and v1. 13 | # It will be removed when all test are rewritten for the new style, like e.g. test/unit/sync/. 14 | # configured by pytest using `--api` option 15 | # check test/unit/conftest.py:pytest_configure for details 16 | from apiver_deps_exception import * 17 | -------------------------------------------------------------------------------- /test/integration/cleanup_buckets.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/integration/cleanup_buckets.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v3.testing import BucketManager, authorize, get_b2_auth_data, get_realm 13 | 14 | from .test_raw_api import cleanup_old_buckets 15 | 16 | if __name__ == '__main__': 17 | cleanup_old_buckets() 18 | BucketManager( 19 | dont_cleanup_old_buckets=False, b2_api=authorize(get_b2_auth_data(), get_realm())[0] 20 | ).clean_buckets() 21 | -------------------------------------------------------------------------------- /b2sdk/version.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/version.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from importlib.metadata import version as _version 13 | from sys import version_info as _version_info 14 | 15 | __all__ = [ 16 | 'VERSION', 17 | 'PYTHON_VERSION', 18 | 'USER_AGENT', 19 | ] 20 | 21 | VERSION = _version('b2sdk') 22 | 23 | PYTHON_VERSION = '.'.join(map(str, _version_info[:3])) # something like: 3.9.1 24 | 25 | USER_AGENT = f'backblaze-b2/{VERSION} python/{PYTHON_VERSION}' 26 | -------------------------------------------------------------------------------- /test/unit/test_included_modules.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/test_included_modules.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import pathlib 13 | 14 | from b2sdk._internal import requests 15 | from b2sdk._internal.requests.included_source_meta import included_source_meta 16 | 17 | 18 | def test_requests_notice_file(): 19 | with (pathlib.Path(requests.__file__).parent / 'NOTICE').open('r') as notice_file: 20 | assert notice_file.read() == included_source_meta.files['NOTICE'] 21 | -------------------------------------------------------------------------------- /doc/source/api/progress.rst: -------------------------------------------------------------------------------- 1 | Progress reporters 2 | ================== 3 | 4 | .. note:: 5 | Concrete classes described in this chapter implement methods defined in ``AbstractProgressListener`` 6 | 7 | .. todo:: 8 | improve documentation of progress reporters 9 | 10 | include info about sync progress 11 | 12 | .. autoclass:: b2sdk.v3.AbstractProgressListener 13 | :inherited-members: 14 | :members: 15 | 16 | .. autoclass:: b2sdk.v3.TqdmProgressListener 17 | :no-members: 18 | 19 | .. autoclass:: b2sdk.v3.SimpleProgressListener 20 | :no-members: 21 | 22 | .. autoclass:: b2sdk.v3.DoNothingProgressListener 23 | :no-members: 24 | 25 | .. autoclass:: b2sdk.v3.ProgressListenerForTest 26 | :no-members: 27 | 28 | .. autofunction:: b2sdk.v3.make_progress_listener 29 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 14 5 | 6 | # Label requiring a response 7 | responseRequiredLabel: more-information-needed 8 | 9 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 10 | closeComment: > 11 | This issue has been automatically closed because there has been no response 12 | to our request for more information from the original author. With only the 13 | information that is currently in the issue, we don't have enough information 14 | to take action. Please reach out if you have or find the answers we need so 15 | that we can investigate or assist you further. 16 | -------------------------------------------------------------------------------- /b2sdk/v0/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v0/__init__.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v1 import * # noqa 13 | from b2sdk.v0.account_info import ( 14 | AbstractAccountInfo, 15 | InMemoryAccountInfo, 16 | UrlPoolAccountInfo, 17 | SqliteAccountInfo, 18 | ) 19 | from b2sdk.v0.api import B2Api 20 | from b2sdk.v0.bucket import Bucket 21 | from b2sdk.v0.bucket import BucketFactory 22 | from b2sdk.v0.sync import Synchronizer 23 | from b2sdk.v0.sync import make_folder_sync_actions 24 | from b2sdk.v0.sync import sync_folders 25 | -------------------------------------------------------------------------------- /b2sdk/_internal/replication/types.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/replication/types.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from enum import Enum, unique 13 | 14 | 15 | @unique 16 | class ReplicationStatus(Enum): 17 | PENDING = 'PENDING' 18 | COMPLETED = 'COMPLETED' 19 | FAILED = 'FAILED' 20 | REPLICA = 'REPLICA' 21 | 22 | @classmethod 23 | def from_response_headers(cls, headers: dict) -> ReplicationStatus | None: 24 | value = headers.get('X-Bz-Replication-Status', None) 25 | return value and cls[value.upper()] 26 | -------------------------------------------------------------------------------- /b2sdk/v1/sync/folder_parser.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v1/sync/folder_parser.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk import v2 13 | from .. import exception 14 | 15 | from .folder import LocalFolder, B2Folder 16 | 17 | 18 | # Override to use v1 version of "LocalFolder" and "B2Folder" and raise old style CommandError 19 | def parse_sync_folder(folder_name, api): 20 | try: 21 | return v2.parse_sync_folder(folder_name, api, LocalFolder, B2Folder) 22 | except exception.InvalidArgument as ex: 23 | raise exception.CommandError(ex.message) 24 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/conftest.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | import concurrent.futures 11 | 12 | import pytest 13 | 14 | pytest_plugins = ['b2sdk.v3.testing'] 15 | 16 | 17 | @pytest.fixture 18 | def bg_executor(): 19 | with concurrent.futures.ThreadPoolExecutor() as executor: 20 | yield executor 21 | 22 | 23 | @pytest.fixture 24 | def apiver_module(): 25 | """ 26 | b2sdk apiver module fixture. 27 | 28 | A compatibility function that is to be replaced by `pytest-apiver` plugin in the future. 29 | """ 30 | import apiver_deps # noqa: F401 31 | 32 | return apiver_deps 33 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.12" 12 | apt_packages: 13 | - graphviz 14 | jobs: 15 | post_create_environment: 16 | - pip install pdm 17 | - pdm export --format requirements --group doc --output requirements-doc.txt 18 | 19 | # Build documentation in the docs/ directory with Sphinx 20 | sphinx: 21 | configuration: doc/source/conf.py 22 | 23 | # Optionally build your docs in additional formats such as PDF and ePub 24 | formats: all 25 | 26 | # Optionally set the version of Python and requirements required to build your docs 27 | python: 28 | install: 29 | - requirements: requirements-doc.txt 30 | - method: pip 31 | path: . 32 | -------------------------------------------------------------------------------- /contrib/debug_logs.ini: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | [loggers] 3 | keys=root,b2 4 | 5 | [logger_root] 6 | level=DEBUG 7 | handlers=fileHandler 8 | 9 | [logger_b2] 10 | level=DEBUG 11 | handlers=fileHandler 12 | qualname=b2 13 | propagate=0 14 | 15 | 16 | 17 | ############################################################ 18 | [handlers] 19 | keys=fileHandler 20 | 21 | [handler_fileHandler] 22 | class=logging.handlers.TimedRotatingFileHandler 23 | level=DEBUG 24 | formatter=simpleFormatter 25 | args=('b2sdk.log', 'midnight') 26 | 27 | 28 | 29 | ############################################################ 30 | [formatters] 31 | keys=simpleFormatter 32 | 33 | [formatter_simpleFormatter] 34 | format=%(asctime)s %(process)d %(thread)d %(name)s %(levelname)s %(message)s 35 | datefmt= 36 | 37 | 38 | 39 | ############################################################ 40 | -------------------------------------------------------------------------------- /b2sdk/v2/b2http.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v2/b2http.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk import v3 13 | from b2sdk.v3.exception import BucketIdNotFound as v3BucketIdNotFound 14 | from .exception import BucketIdNotFound 15 | 16 | 17 | # Overridden to retain old-style BadRequest exception in case of a bad bucket id 18 | class B2Http(v3.B2Http): 19 | @classmethod 20 | def _translate_errors(cls, fcn, post_params=None): 21 | try: 22 | return super()._translate_errors(fcn, post_params) 23 | except v3BucketIdNotFound as e: 24 | raise BucketIdNotFound(e.bucket_id) 25 | -------------------------------------------------------------------------------- /b2sdk/v0/exception.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v0/exception.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | B2Error = None # calm down, pyflakes 13 | 14 | from b2sdk.v1.exception import * # noqa 15 | 16 | v1DestFileNewer = DestFileNewer 17 | 18 | 19 | # override to retain old style __str__ 20 | class DestFileNewer(v1DestFileNewer): 21 | def __str__(self): 22 | return f'source file is older than destination: {self.source_prefix}{self.source_file.name} with a time of {self.source_file.latest_version().mod_time} cannot be synced to {self.dest_prefix}{self.dest_file.name} with a time of {self.dest_file.latest_version().mod_time}, unless --skipNewer or --replaceNewer is provided' 23 | -------------------------------------------------------------------------------- /b2sdk/_internal/requests/included_source_meta.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/requests/included_source_meta.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from b2sdk._internal.included_sources import IncludedSourceMeta, add_included_source 11 | 12 | included_source_meta = IncludedSourceMeta( 13 | 'requests', 14 | 'Included in a revised form', 15 | { 16 | 'NOTICE': """Requests 17 | Copyright 2019 Kenneth Reitz 18 | 19 | Copyright 2021 Backblaze Inc. 20 | Changes made to the original source: 21 | requests.models.Response.iter_content has been overridden to pass `decode_content=False` argument to `self.raw.stream` 22 | in order to NOT decompress data based on Content-Encoding header""" 23 | }, 24 | ) 25 | add_included_source(included_source_meta) 26 | -------------------------------------------------------------------------------- /b2sdk/v1/sync/report.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v1/sync/report.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk import v2 13 | 14 | 15 | # override to retain legacy methods 16 | class SyncReport(v2.SyncReport): 17 | @property 18 | def local_file_count(self): 19 | return self.total_count 20 | 21 | @local_file_count.setter 22 | def local_file_count(self, value): 23 | self.total_count = value 24 | 25 | @property 26 | def local_done(self): 27 | return self.total_done 28 | 29 | @local_done.setter 30 | def local_done(self, value): 31 | self.total_done = value 32 | 33 | update_local = v2.SyncReport.update_total 34 | end_local = v2.SyncReport.end_total 35 | -------------------------------------------------------------------------------- /b2sdk/_internal/included_sources.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/included_sources.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | # This module provides a list of third party sources included and modified in b2sdk, so it can be exposed to 13 | # B2 Command Line Tool for printing, for legal compliance reasons 14 | import dataclasses 15 | 16 | _included_sources: list[IncludedSourceMeta] = [] 17 | 18 | 19 | @dataclasses.dataclass 20 | class IncludedSourceMeta: 21 | name: str 22 | comment: str 23 | files: dict[str, str] 24 | 25 | 26 | def add_included_source(src: IncludedSourceMeta): 27 | _included_sources.append(src) 28 | 29 | 30 | def get_included_sources() -> list[IncludedSourceMeta]: 31 | return _included_sources 32 | -------------------------------------------------------------------------------- /test/unit/fixtures/session.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/fixtures/session.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import pytest 13 | from apiver_deps import B2Session 14 | 15 | 16 | @pytest.fixture 17 | def b2_session(fake_account_info, fake_cache, fake_b2_raw_api): 18 | session = B2Session(account_info=fake_account_info, cache=fake_cache) 19 | session.raw_api = fake_b2_raw_api 20 | return session 21 | 22 | 23 | @pytest.fixture 24 | def fake_b2_session(mocker, fake_account_info, fake_cache, fake_b2_raw_api): 25 | b2_session = mocker.MagicMock(name='FakeB2Session', spec=B2Session) 26 | b2_session.account_info = fake_account_info 27 | b2_session.cache = fake_cache 28 | b2_session.raw_api = fake_b2_raw_api 29 | return b2_session 30 | -------------------------------------------------------------------------------- /doc/source/api/file_lock.rst: -------------------------------------------------------------------------------- 1 | File locks 2 | ========== 3 | 4 | .. autoclass:: b2sdk.v3.LegalHold() 5 | :no-members: 6 | :special-members: ON, OFF, UNSET, UNKNOWN, is_on, is_off, is_unknown 7 | 8 | .. autoclass:: b2sdk.v3.FileRetentionSetting() 9 | :no-members: 10 | :special-members: __init__ 11 | 12 | .. autoclass:: b2sdk.v3.RetentionMode() 13 | :inherited-members: 14 | :members: 15 | 16 | .. autoclass:: b2sdk.v3.BucketRetentionSetting() 17 | :no-members: 18 | :special-members: __init__ 19 | 20 | .. autoclass:: b2sdk.v3.RetentionPeriod() 21 | :inherited-members: 22 | :special-members: __init__ 23 | 24 | .. autoclass:: b2sdk.v3.FileLockConfiguration() 25 | :no-members: 26 | :special-members: __init__ 27 | 28 | .. autoclass:: b2sdk.v3.UNKNOWN_BUCKET_RETENTION() 29 | 30 | .. autoclass:: b2sdk.v3.UNKNOWN_FILE_LOCK_CONFIGURATION() 31 | 32 | .. autoclass:: b2sdk.v3.NO_RETENTION_BUCKET_SETTING() 33 | 34 | .. autoclass:: b2sdk.v3.NO_RETENTION_FILE_SETTING() 35 | 36 | .. autoclass:: b2sdk.v3.UNKNOWN_FILE_RETENTION_SETTING() 37 | -------------------------------------------------------------------------------- /test/static/test_licenses.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/static/test_licenses.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from glob import glob 13 | from itertools import islice 14 | 15 | import pytest 16 | 17 | 18 | def test_files_headers(): 19 | for file in glob('**/*.py', recursive=True): 20 | with open(file) as fd: 21 | file = file.replace( 22 | '\\', '/' 23 | ) # glob('**/*.py') on Windows returns "b2\bucket.py" (wrong slash) 24 | head = ''.join(islice(fd, 9)) 25 | if 'All Rights Reserved' not in head: 26 | pytest.fail(f'Missing "All Rights Reserved" in the header in: {file}') 27 | if file not in head: 28 | pytest.fail(f'Wrong file name in the header in: {file}') 29 | -------------------------------------------------------------------------------- /b2sdk/_internal/utils/filesystem.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/utils/filesystem.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | import pathlib 11 | import platform 12 | import stat 13 | 14 | _IS_WINDOWS = platform.system() == 'Windows' 15 | 16 | 17 | def points_to_fifo(path: pathlib.Path) -> bool: 18 | """Check if the path points to a fifo.""" 19 | path = path.resolve() 20 | try: 21 | return stat.S_ISFIFO(path.stat().st_mode) 22 | except OSError: 23 | return False 24 | 25 | 26 | _STDOUT_FILENAME = 'CON' if _IS_WINDOWS else '/dev/stdout' 27 | STDOUT_FILEPATH = pathlib.Path(_STDOUT_FILENAME) 28 | 29 | 30 | def points_to_stdout(path: pathlib.Path) -> bool: 31 | """Check if the path points to stdout.""" 32 | try: 33 | return path == STDOUT_FILEPATH or path.resolve() == STDOUT_FILEPATH 34 | except OSError: 35 | return False 36 | -------------------------------------------------------------------------------- /b2sdk/v0/api.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v0/api.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from .bucket import Bucket, BucketFactory 13 | from b2sdk import v1 14 | 15 | 16 | class B2Api(v1.B2Api): 17 | BUCKET_FACTORY_CLASS = staticmethod(BucketFactory) 18 | BUCKET_CLASS = staticmethod(Bucket) 19 | 20 | def delete_bucket(self, bucket): 21 | """ 22 | Delete the chosen bucket. 23 | 24 | For legacy reasons it returns whatever server sends in response, 25 | but API user should not rely on the response: if it doesn't raise 26 | an exception, it means that the operation was a success. 27 | 28 | :param b2sdk.v1.Bucket bucket: a :term:`bucket` to delete 29 | """ 30 | account_id = self.account_info.get_account_id() 31 | return self.session.delete_bucket(account_id, bucket.id_) 32 | -------------------------------------------------------------------------------- /b2sdk/v2/exception.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v2/exception.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v3.exception import * # noqa 13 | 14 | v3BucketIdNotFound = BucketIdNotFound 15 | UnSyncableFilename = UnsupportedFilename 16 | 17 | 18 | # overridden to retain old style isinstance check and attributes 19 | class BucketIdNotFound(v3BucketIdNotFound, BadRequest): 20 | def __init__(self, bucket_id): 21 | super().__init__(bucket_id) 22 | self.message = f'Bucket with id={bucket_id} not found' 23 | self.code = 'bad_bucket_id' 24 | 25 | def __str__(self): 26 | return BadRequest.__str__(self) 27 | 28 | 29 | class RestrictedBucket(B2Error): 30 | def __init__(self, bucket_name): 31 | super().__init__() 32 | self.bucket_name = bucket_name 33 | 34 | def __str__(self): 35 | return 'Application key is restricted to bucket: %s' % self.bucket_name 36 | -------------------------------------------------------------------------------- /b2sdk/v2/utils.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v2/utils.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | 11 | from __future__ import annotations 12 | 13 | import shutil 14 | import tempfile 15 | import warnings 16 | 17 | 18 | class TempDir: 19 | """ 20 | Context manager that creates and destroys a temporary directory. 21 | """ 22 | 23 | def __enter__(self): 24 | """ 25 | Return the unicode path to the temp dir. 26 | """ 27 | warnings.warn( 28 | 'TempDir is deprecated. Use tempfile.TemporaryDirectory or pytest tmp_path fixture instead.', 29 | DeprecationWarning, 30 | stacklevel=2, 31 | ) 32 | dirpath_bytes = tempfile.mkdtemp() 33 | self.dirpath = str(dirpath_bytes.replace('\\', '\\\\')) 34 | return self.dirpath 35 | 36 | def __exit__(self, exc_type, exc_val, exc_tb): 37 | shutil.rmtree(self.dirpath) 38 | return None # do not hide exception 39 | -------------------------------------------------------------------------------- /test/unit/v_all/test_constants.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v_all/test_constants.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import apiver_deps 13 | import pytest 14 | 15 | 16 | @pytest.mark.apiver(from_ver=2) 17 | def test_public_constants(): 18 | assert set(dir(apiver_deps)) >= { 19 | 'ALL_CAPABILITIES', 20 | 'B2_ACCOUNT_INFO_DEFAULT_FILE', 21 | 'B2_ACCOUNT_INFO_ENV_VAR', 22 | 'B2_ACCOUNT_INFO_PROFILE_FILE', 23 | 'DEFAULT_MIN_PART_SIZE', 24 | 'DEFAULT_RECOMMENDED_UPLOAD_PART_SIZE', 25 | 'LARGE_FILE_SHA1', 26 | 'LIST_FILE_NAMES_MAX_LIMIT', 27 | 'SERVER_DEFAULT_SYNC_ENCRYPTION_SETTINGS_PROVIDER', 28 | 'SRC_LAST_MODIFIED_MILLIS', 29 | 'SSE_B2_AES', 30 | 'SSE_C_KEY_ID_FILE_INFO_KEY_NAME', 31 | 'SSE_NONE', 32 | 'UNKNOWN_KEY_ID', 33 | 'V', 34 | 'VERSION', 35 | 'XDG_CONFIG_HOME_ENV_VAR', 36 | } 37 | -------------------------------------------------------------------------------- /b2sdk/v0/bucket.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v0/bucket.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk import v1 13 | 14 | 15 | class Bucket(v1.Bucket): 16 | def list_file_names(self, start_filename=None, max_entries=None, prefix=None): 17 | """ 18 | Legacy interface which just returns whatever remote API returns. 19 | """ 20 | return self.api.session.list_file_names(self.id_, start_filename, max_entries, prefix) 21 | 22 | def list_file_versions( 23 | self, start_filename=None, start_file_id=None, max_entries=None, prefix=None 24 | ): 25 | """ 26 | Legacy interface which just returns whatever remote API returns. 27 | """ 28 | return self.api.session.list_file_versions( 29 | self.id_, start_filename, start_file_id, max_entries, prefix 30 | ) 31 | 32 | 33 | class BucketFactory(v1.BucketFactory): 34 | BUCKET_CLASS = staticmethod(Bucket) 35 | -------------------------------------------------------------------------------- /test/unit/test_progress.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/test_progress.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | import pytest 11 | from apiver_deps import TqdmProgressListener, make_progress_listener 12 | 13 | 14 | @pytest.mark.parametrize( 15 | 'tqdm_available, quiet, expected_listener', 16 | [ 17 | (True, False, 'TqdmProgressListener'), 18 | (False, False, 'SimpleProgressListener'), 19 | (False, True, 'DoNothingProgressListener'), 20 | ], 21 | ) 22 | def test_make_progress_listener(tqdm_available, quiet, expected_listener, monkeypatch): 23 | if not tqdm_available: 24 | monkeypatch.setattr('b2sdk._internal.progress.tqdm', None) 25 | 26 | assert make_progress_listener('description', quiet).__class__.__name__ == expected_listener 27 | 28 | 29 | def test_tqdm_progress_listener__without_tqdm_module(monkeypatch): 30 | monkeypatch.setattr('b2sdk._internal.progress.tqdm', None) 31 | 32 | with pytest.raises(ModuleNotFoundError): 33 | TqdmProgressListener('description') 34 | -------------------------------------------------------------------------------- /doc/bash_completion.md: -------------------------------------------------------------------------------- 1 | In order to use bash completion, you can have a `~/.bash_completion` like this: 2 | ```sh 3 | if [ -d "$HOME/.bash_completion.d" ]; then 4 | for file in "$HOME/.bash_completion.d/"* 5 | do 6 | source "$file" >/dev/null 2>&1 7 | done 8 | fi 9 | ``` 10 | and then copy our `contrib/bash_completion/b2` to your `~/.bash_completion.d/`. 11 | 12 | The important trick is that `b2` tool must be in PATH before bash_completions are loaded for the last time (unless you delete the first line of our completion script). 13 | 14 | If you keep the `b2` tool in `~/bin`, you can make sure the loading order is proper by making sure `~/bin` is added to the PATH before loading bash_completion. To do that, add the following snippet to your `~/.bashrc`: 15 | 16 | ```sh 17 | if [ -d ~/bin ]; then 18 | PATH="$HOME/bin:$PATH" 19 | fi 20 | 21 | # enable programmable completion features (you don't need to enable 22 | # this, if it's already enabled in /etc/bash.bashrc and /etc/profile 23 | # sources /etc/bash.bashrc). 24 | if ! shopt -oq posix; then 25 | if [ -f /usr/share/bash-completion/bash_completion ]; then 26 | . /usr/share/bash-completion/bash_completion 27 | elif [ -f /etc/bash_completion ]; then 28 | . /etc/bash_completion 29 | fi 30 | fi 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /doc/sqlite_account_info_schema.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: doc/sqlite_account_info_schema.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | """ generates a dot file with SqliteAccountInfo database structure """ 11 | from __future__ import annotations 12 | 13 | import tempfile 14 | import operator 15 | 16 | from sadisplay import describe, render 17 | from sqlalchemy import create_engine, MetaData 18 | 19 | from b2sdk._internal.account_info.sqlite_account_info import SqliteAccountInfo 20 | 21 | 22 | def main(): 23 | with tempfile.NamedTemporaryFile() as fp: 24 | sqlite_db_name = fp.name 25 | SqliteAccountInfo(sqlite_db_name) 26 | engine = create_engine('sqlite:///' + sqlite_db_name) 27 | 28 | meta = MetaData() 29 | 30 | meta.reflect(bind=engine) 31 | 32 | tables = set(meta.tables.keys()) 33 | 34 | desc = describe(map(lambda x: operator.getitem(meta.tables, x), sorted(tables))) 35 | print(getattr(render, 'dot')(desc).encode('utf-8')) 36 | 37 | 38 | if __name__ == '__main__': 39 | main() 40 | -------------------------------------------------------------------------------- /b2sdk/_internal/types.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/types.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | """ 11 | Types compatibility layer. 12 | 13 | We use this module to support pydantic-less installs, as well as native typing module us on newer python versions. 14 | """ 15 | 16 | import sys 17 | 18 | from annotated_types import Ge 19 | 20 | try: 21 | from typing_extensions import Annotated, NotRequired, TypedDict 22 | except ImportError: 23 | from typing import Annotated, NotRequired, TypedDict 24 | 25 | __all__ = [ # prevents linter from removing "unused imports" which we want to export 26 | 'NotRequired', 27 | 'PositiveInt', 28 | 'TypedDict', 29 | 'pydantic', 30 | ] 31 | 32 | try: 33 | import pydantic 34 | 35 | if getattr(pydantic, '__version__', '') < '2': 36 | raise ImportError 37 | 38 | if sys.version_info < (3, 10): # https://github.com/pydantic/pydantic/issues/7873 39 | import eval_type_backport # noqa 40 | except ImportError: 41 | pydantic = None 42 | 43 | PositiveInt = Annotated[int, Ge(0)] 44 | -------------------------------------------------------------------------------- /b2sdk/v1/replication/monitoring.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v1/replication/monitoring.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from dataclasses import dataclass 13 | 14 | import b2sdk.v2 as v2 15 | 16 | from .. import Bucket 17 | from ..sync.folder import B2Folder 18 | 19 | 20 | @dataclass 21 | class ReplicationMonitor(v2.ReplicationMonitor): 22 | # when passing in v1 Bucket objects to ReplicationMonitor, 23 | # the latter should use v1 B2Folder to correctly use 24 | # v1 Bucket's interface 25 | B2_FOLDER_CLASS = B2Folder 26 | 27 | @property 28 | def destination_bucket(self) -> Bucket: 29 | destination_api = self.destination_api or self.source_api 30 | bucket_id = self.rule.destination_bucket_id 31 | 32 | # when using `destination_api.get_bucket_by_id(bucket_id)`, 33 | # v1 will instantiate the bucket without its name, but we need it, 34 | # so we use `list_buckets` to actually fetch bucket name 35 | return destination_api.list_buckets(bucket_id=bucket_id)[0] 36 | -------------------------------------------------------------------------------- /b2sdk/_internal/utils/http_date.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/utils/http_date.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | import datetime as dt 11 | 12 | 13 | def parse_http_date(timestamp_str: str) -> dt.datetime: 14 | # See https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1 15 | # for the list of supported formats. 16 | # We don't parse non-GTM dates because they are not valid HTTP-dates 17 | # as defined in RFC7231 7.1.1.1. Backblaze is more premissive than 18 | # the standard here. 19 | http_data_formats = [ 20 | '%a, %d %b %Y %H:%M:%S GMT', # IMF-fixdate 21 | '%A, %d-%b-%y %H:%M:%S GMT', # obsolete RFC 850 format 22 | '%a %b %d %H:%M:%S %Y', # ANSI C's asctime() format 23 | ] 24 | for format in http_data_formats: 25 | try: 26 | timestamp = dt.datetime.strptime(timestamp_str, format) 27 | return timestamp.replace(tzinfo=dt.timezone.utc) 28 | except ValueError: 29 | pass 30 | raise ValueError("Value %s is not a valid HTTP-date, won't be parsed.", timestamp_str) 31 | -------------------------------------------------------------------------------- /test/unit/v_all/test_transfer.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v_all/test_transfer.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from unittest.mock import Mock 13 | 14 | from apiver_deps import DownloadManager, UploadManager 15 | 16 | from ..test_base import TestBase 17 | 18 | 19 | class TestDownloadManager(TestBase): 20 | def test_set_thread_pool_size(self) -> None: 21 | download_manager = DownloadManager(services=Mock()) 22 | assert download_manager.get_thread_pool_size() > 0 23 | 24 | pool_size = 21 25 | download_manager.set_thread_pool_size(pool_size) 26 | assert download_manager.get_thread_pool_size() == pool_size 27 | 28 | 29 | class TestUploadManager(TestBase): 30 | def test_set_thread_pool_size(self) -> None: 31 | upload_manager = UploadManager(services=Mock()) 32 | assert upload_manager.get_thread_pool_size() > 0 33 | 34 | pool_size = 37 35 | upload_manager.set_thread_pool_size(pool_size) 36 | assert upload_manager.get_thread_pool_size() == pool_size 37 | -------------------------------------------------------------------------------- /b2sdk/_internal/testing/fixtures/api.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/testing/fixtures/api.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import http 13 | import http.client 14 | import os 15 | 16 | import pytest 17 | 18 | from b2sdk._internal.testing.helpers.api import authorize, get_b2_auth_data, get_realm 19 | 20 | 21 | @pytest.fixture(scope='session') 22 | def realm(): 23 | yield get_realm() 24 | 25 | 26 | @pytest.fixture(autouse=True, scope='session') 27 | def set_http_debug(): 28 | if os.environ.get('B2_DEBUG_HTTP'): 29 | http.client.HTTPConnection.debuglevel = 1 30 | 31 | 32 | @pytest.fixture(scope='session') 33 | def b2_auth_data(): 34 | try: 35 | return get_b2_auth_data() 36 | except ValueError as ex: 37 | pytest.fail(ex.args[0]) 38 | 39 | 40 | @pytest.fixture(scope='session') 41 | def _b2_api(b2_auth_data, realm): 42 | b2_api, _ = authorize(b2_auth_data, realm) 43 | return b2_api 44 | 45 | 46 | @pytest.fixture(scope='session') 47 | def b2_api(_b2_api, bucket_manager): 48 | return _b2_api 49 | -------------------------------------------------------------------------------- /test/unit/utils/test_thread_pool.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/utils/test_thread_pool.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from concurrent.futures import Future 11 | 12 | import pytest 13 | 14 | from b2sdk._internal.utils.thread_pool import LazyThreadPool 15 | 16 | 17 | class TestLazyThreadPool: 18 | @pytest.fixture 19 | def thread_pool(self): 20 | return LazyThreadPool() 21 | 22 | def test_submit(self, thread_pool): 23 | future = thread_pool.submit(sum, (1, 2)) 24 | assert isinstance(future, Future) 25 | assert future.result() == 3 26 | 27 | def test_set_size(self, thread_pool): 28 | thread_pool.set_size(10) 29 | assert thread_pool.get_size() == 10 30 | 31 | def test_get_size(self, thread_pool): 32 | assert thread_pool.get_size() > 0 33 | 34 | def test_set_size__after_submit(self, thread_pool): 35 | future = thread_pool.submit(sum, (1, 2)) 36 | 37 | thread_pool.set_size(7) 38 | assert thread_pool.get_size() == 7 39 | 40 | assert future.result() == 3 41 | 42 | assert thread_pool.submit(sum, (1,)).result() == 1 43 | -------------------------------------------------------------------------------- /b2sdk/_internal/testing/helpers/api.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/testing/helpers/api.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import os 13 | 14 | from b2sdk._internal.account_info.in_memory import InMemoryAccountInfo 15 | from b2sdk._internal.api import B2Api 16 | from b2sdk._internal.api_config import DEFAULT_HTTP_API_CONFIG 17 | 18 | 19 | def get_realm() -> str: 20 | return os.environ.get('B2_TEST_ENVIRONMENT', 'production') 21 | 22 | 23 | def get_b2_auth_data(): 24 | application_key_id = os.environ.get('B2_TEST_APPLICATION_KEY_ID') 25 | if application_key_id is None: 26 | raise ValueError('B2_TEST_APPLICATION_KEY_ID is not set.') 27 | 28 | application_key = os.environ.get('B2_TEST_APPLICATION_KEY') 29 | if application_key is None: 30 | raise ValueError('B2_TEST_APPLICATION_KEY is not set.') 31 | return application_key_id, application_key 32 | 33 | 34 | def authorize(b2_auth_data, realm, api_config=DEFAULT_HTTP_API_CONFIG): 35 | info = InMemoryAccountInfo() 36 | b2_api = B2Api(info, api_config=api_config) 37 | b2_api.authorize_account(*b2_auth_data, realm=realm) 38 | return b2_api, info 39 | -------------------------------------------------------------------------------- /test/unit/utils/test_docs.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/utils/test_docs.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import pytest 13 | 14 | from b2sdk._internal.raw_api import AbstractRawApi, LifecycleRule 15 | from b2sdk._internal.utils.docs import MissingDocURL, ensure_b2sdk_doc_urls, get_b2sdk_doc_urls 16 | 17 | 18 | def test_b2sdk_doc_urls(): 19 | @ensure_b2sdk_doc_urls 20 | class MyCustomClass: 21 | """ 22 | This is a custom class with `Documentation URL`_. 23 | 24 | .. _Documentation URL: https://example.com 25 | """ 26 | 27 | 28 | def test_b2sdk_doc_urls__no_urls_error(): 29 | with pytest.raises(MissingDocURL): 30 | 31 | @ensure_b2sdk_doc_urls 32 | class MyCustomClass: 33 | pass 34 | 35 | 36 | @pytest.mark.parametrize( 37 | 'type_,expected', 38 | [ 39 | (AbstractRawApi, {}), 40 | ( 41 | LifecycleRule, 42 | { 43 | 'B2 Cloud Storage Lifecycle Rules': 'https://www.backblaze.com/docs/cloud-storage-lifecycle-rules', 44 | }, 45 | ), 46 | ], 47 | ) 48 | def test_get_b2sdk_doc_urls(type_, expected): 49 | assert get_b2sdk_doc_urls(type_) == expected 50 | -------------------------------------------------------------------------------- /b2sdk/v2/large_file.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v2/large_file.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk import v3 13 | 14 | 15 | class UnfinishedLargeFile(v3.UnfinishedLargeFile): 16 | """ 17 | A structure which represents a version of a file (in B2 cloud). 18 | 19 | :ivar str ~.file_id: ``fileId`` 20 | :ivar str ~.file_name: full file name (with path) 21 | :ivar str ~.account_id: account ID 22 | :ivar str ~.bucket_id: bucket ID 23 | :ivar str ~.content_type: :rfc:`822` content type, for example ``"application/octet-stream"`` 24 | :ivar dict ~.file_info: file info dict 25 | """ 26 | 27 | # In v3, cache_control is a property. 28 | # We set this to None so that it can be assigned to in __init__. 29 | cache_control = None 30 | 31 | def __init__(self, file_dict): 32 | """ 33 | Initialize from one file returned by ``b2_start_large_file`` or ``b2_list_unfinished_large_files``. 34 | """ 35 | super().__init__(file_dict) 36 | self.cache_control = (file_dict['fileInfo'] or {}).get('b2-cache-control') 37 | 38 | 39 | class LargeFileServices(v3.LargeFileServices): 40 | UNFINISHED_LARGE_FILE_CLASS = staticmethod(UnfinishedLargeFile) 41 | -------------------------------------------------------------------------------- /b2sdk/v3/testing/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v3/testing/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | # testing - it is not imported in v3.__init__ as it depends on pytest and other test dependencies. 13 | 14 | from b2sdk._internal.testing.helpers.api import get_b2_auth_data, authorize, get_realm 15 | from b2sdk._internal.testing.helpers.base import IntegrationTestBase 16 | from b2sdk._internal.testing.helpers.buckets import ( 17 | GENERAL_BUCKET_NAME_PREFIX, 18 | BUCKET_NAME_LENGTH, 19 | BUCKET_CREATED_AT_MILLIS, 20 | NODE_DESCRIPTION, 21 | RNG, 22 | RNG_COUNTER, 23 | RNG_SEED, 24 | get_bucket_name_prefix, 25 | random_token, 26 | ) 27 | from b2sdk._internal.testing.helpers.bucket_manager import ( 28 | ONE_HOUR_MILLIS, 29 | BUCKET_CLEANUP_PERIOD_MILLIS, 30 | BucketManager, 31 | ) 32 | from b2sdk._internal.testing.fixtures.api import ( 33 | set_http_debug, 34 | b2_auth_data, 35 | _b2_api, 36 | b2_api, 37 | realm, 38 | ) 39 | from b2sdk._internal.testing.fixtures.buckets import ( 40 | pytest_addoption, 41 | dont_cleanup_old_buckets, 42 | bucket_name_prefix, 43 | general_bucket_name_prefix, 44 | bucket_manager, 45 | bucket, 46 | b2_subfolder, 47 | ) 48 | -------------------------------------------------------------------------------- /test/unit/filter/test_filter.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/filter/test_filter.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import pytest 13 | from apiver_deps import Filter 14 | 15 | from b2sdk._internal.filter import FilterMatcher 16 | 17 | 18 | @pytest.mark.parametrize( 19 | ('filters', 'expr', 'expected'), 20 | ( 21 | ([], 'a', True), 22 | ([Filter.exclude('*')], 'something', False), 23 | ([Filter.include('a-*')], 'a-', True), 24 | ([Filter.include('a-*')], 'b-', False), 25 | ([Filter.exclude('*.txt')], 'a.txt', False), 26 | ([Filter.exclude('*.txt')], 'a.csv', True), 27 | ([Filter.exclude('*'), Filter.include('*.[ct]sv')], 'a.csv', True), 28 | ([Filter.exclude('*'), Filter.include('*.[ct]sv')], 'a.tsv', True), 29 | ([Filter.exclude('*'), Filter.include('*.[ct]sv')], 'a.ksv', False), 30 | ( 31 | [Filter.exclude('*'), Filter.include('*.[ct]sv'), Filter.exclude('a.csv')], 32 | 'a.csv', 33 | False, 34 | ), 35 | ([Filter.exclude('*'), Filter.include('*.[ct]sv'), Filter.exclude('a.csv')], 'b.csv', True), 36 | ), 37 | ) 38 | def test_filter_matcher(filters, expr, expected): 39 | assert FilterMatcher(filters).match(expr) == expected 40 | -------------------------------------------------------------------------------- /b2sdk/v2/version_utils.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v2/version_utils.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk import v3 13 | 14 | 15 | class _OldAbstractDeprecatorMixin: 16 | def __call__(self, *args, **kwargs): 17 | if self.cutoff_version: 18 | assert ( 19 | self.current_version < self.cutoff_version 20 | ), f'{self.__class__.__name__} decorator is still used in version {self.current_version} when old {self.WHAT} name {self.source!r} was scheduled to be dropped in {self.cutoff_version}. It is time to remove the mapping.' 21 | ret = super().__call__(*args, **kwargs) 22 | assert ( 23 | self.changed_version <= self.current_version 24 | ), f'{self.__class__.__name__} decorator indicates that the replacement of {self.WHAT} {self.source!r} should take place in the future version {self.changed_version}, while the current version is {self.cutoff_version}. It looks like should be _discouraged_ at this point and not _deprecated_ yet. Consider using {self.ALTERNATIVE_DECORATOR!r} decorator instead.' 25 | return ret 26 | 27 | 28 | class rename_argument(_OldAbstractDeprecatorMixin, v3.rename_argument): 29 | pass 30 | 31 | 32 | class rename_function(_OldAbstractDeprecatorMixin, v3.rename_function): 33 | pass 34 | -------------------------------------------------------------------------------- /b2sdk/_internal/encryption/types.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/encryption/types.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from enum import Enum, unique 13 | 14 | 15 | @unique 16 | class EncryptionAlgorithm(Enum): 17 | """Encryption algorithm.""" 18 | 19 | AES256 = 'AES256' 20 | 21 | def get_length(self) -> int: 22 | if self is EncryptionAlgorithm.AES256: 23 | return int(256 / 8) 24 | 25 | raise NotImplementedError() 26 | 27 | 28 | @unique 29 | class EncryptionMode(Enum): 30 | """Encryption mode.""" 31 | 32 | UNKNOWN = None #: unknown encryption mode (sdk doesn't know or used key has no rights to know) 33 | NONE = 'none' #: no encryption (plaintext) 34 | SSE_B2 = 'SSE-B2' #: server-side encryption with key maintained by B2 35 | SSE_C = 'SSE-C' #: server-side encryption with key provided by the client 36 | 37 | # CLIENT = 'CLIENT' #: client-side encryption 38 | 39 | def can_be_set_as_bucket_default(self): 40 | return self in BUCKET_DEFAULT_ENCRYPTION_MODES 41 | 42 | 43 | ENCRYPTION_MODES_WITH_MANDATORY_ALGORITHM = frozenset((EncryptionMode.SSE_B2, EncryptionMode.SSE_C)) 44 | ENCRYPTION_MODES_WITH_MANDATORY_KEY = frozenset((EncryptionMode.SSE_C,)) 45 | BUCKET_DEFAULT_ENCRYPTION_MODES = frozenset((EncryptionMode.NONE, EncryptionMode.SSE_B2)) 46 | -------------------------------------------------------------------------------- /b2sdk/v1/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v1/__init__.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v2 import * # noqa 13 | from b2sdk.v1.account_info import ( 14 | AbstractAccountInfo, 15 | InMemoryAccountInfo, 16 | UrlPoolAccountInfo, 17 | SqliteAccountInfo, 18 | StubAccountInfo, 19 | ) 20 | from b2sdk.v1.api import B2Api 21 | from b2sdk.v1.b2http import B2Http 22 | from b2sdk.v1.bucket import Bucket, BucketFactory 23 | from b2sdk.v1.cache import AbstractCache 24 | from b2sdk.v1.download_dest import ( 25 | AbstractDownloadDestination, 26 | DownloadDestLocalFile, 27 | PreSeekedDownloadDest, 28 | DownloadDestBytes, 29 | DownloadDestProgressWrapper, 30 | ) 31 | from b2sdk.v1.exception import CommandError, DestFileNewer 32 | from b2sdk.v1.file_metadata import FileMetadata 33 | from b2sdk.v1.file_version import FileVersionInfo 34 | from b2sdk.v1.session import B2Session 35 | from b2sdk.v1.sync import ( 36 | ScanPoliciesManager, 37 | DEFAULT_SCAN_MANAGER, 38 | zip_folders, 39 | Synchronizer, 40 | AbstractFolder, 41 | LocalFolder, 42 | B2Folder, 43 | parse_sync_folder, 44 | SyncReport, 45 | File, 46 | B2File, 47 | FileVersion, 48 | AbstractSyncEncryptionSettingsProvider, 49 | ) 50 | from b2sdk.v1.replication.monitoring import ReplicationMonitor 51 | 52 | B2RawApi = B2RawHTTPApi 53 | -------------------------------------------------------------------------------- /b2sdk/v1/exception.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v1/exception.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v2.exception import * # noqa 13 | 14 | v2DestFileNewer = DestFileNewer 15 | 16 | 17 | # This exception class is deprecated and should not be used in new designs 18 | class CommandError(B2Error): 19 | """ 20 | b2 command error (user caused). Accepts exactly one argument: message. 21 | 22 | We expect users of shell scripts will parse our ``__str__`` output. 23 | """ 24 | 25 | def __init__(self, message): 26 | super().__init__() 27 | self.message = message 28 | 29 | def __str__(self): 30 | return self.message 31 | 32 | 33 | class DestFileNewer(v2DestFileNewer): 34 | def __init__(self, dest_file, source_file, dest_prefix, source_prefix): 35 | super(v2DestFileNewer, self).__init__() 36 | self.dest_file = dest_file 37 | self.source_file = source_file 38 | self.dest_prefix = dest_prefix 39 | self.source_prefix = source_prefix 40 | 41 | def __str__(self): 42 | return f'source file is older than destination: {self.source_prefix}{self.source_file.name} with a time of {self.source_file.latest_version().mod_time} cannot be synced to {self.dest_prefix}{self.dest_file.name} with a time of {self.dest_file.latest_version().mod_time}, unless a valid newer_file_mode is provided' 43 | -------------------------------------------------------------------------------- /b2sdk/_internal/large_file/part.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/large_file/part.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | 13 | class PartFactory: 14 | @classmethod 15 | def from_list_parts_dict(cls, part_dict): 16 | return Part( 17 | part_dict['fileId'], 18 | part_dict['partNumber'], 19 | part_dict['contentLength'], 20 | part_dict['contentSha1'], 21 | ) 22 | 23 | 24 | class Part: 25 | """ 26 | A structure which represents a *part* of a large file upload. 27 | 28 | :ivar str ~.file_id: ``fileId`` 29 | :ivar int ~.part_number: part number, starting with 1 30 | :ivar str ~.content_length: content length, in bytes 31 | :ivar str ~.content_sha1: checksum 32 | """ 33 | 34 | def __init__(self, file_id, part_number, content_length, content_sha1): 35 | self.file_id = file_id 36 | self.part_number = part_number 37 | self.content_length = content_length 38 | self.content_sha1 = content_sha1 39 | 40 | def __repr__(self): 41 | return f'<{self.__class__.__name__} {self.file_id} {self.part_number} {self.content_length} {self.content_sha1}>' 42 | 43 | def __eq__(self, other): 44 | return isinstance(other, self.__class__) and self.__dict__ == other.__dict__ 45 | 46 | def __ne__(self, other): 47 | return not (self == other) 48 | -------------------------------------------------------------------------------- /b2sdk/_internal/account_info/exception.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/account_info/exception.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from abc import ABCMeta 13 | 14 | from ..exception import B2Error 15 | 16 | 17 | class AccountInfoError(B2Error, metaclass=ABCMeta): 18 | """ 19 | Base class for all account info errors. 20 | """ 21 | 22 | pass 23 | 24 | 25 | class CorruptAccountInfo(AccountInfoError): 26 | """ 27 | Raised when an account info file is corrupted. 28 | """ 29 | 30 | def __init__(self, file_name): 31 | """ 32 | :param file_name: an account info file name 33 | :type file_name: str 34 | """ 35 | super().__init__() 36 | self.file_name = file_name 37 | 38 | def __str__(self): 39 | return ( 40 | f'Account info file ({self.file_name}) appears corrupted. ' 41 | f'Try removing and then re-authorizing the account.' 42 | ) 43 | 44 | 45 | class MissingAccountData(AccountInfoError): 46 | """ 47 | Raised when there is no account info data available. 48 | """ 49 | 50 | def __init__(self, key): 51 | """ 52 | :param key: a key for getting account data 53 | :type key: str 54 | """ 55 | super().__init__() 56 | self.key = key 57 | 58 | def __str__(self): 59 | return f'Missing account data: {self.key}' 60 | -------------------------------------------------------------------------------- /test/unit/scan/test_folder.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/scan/test_folder.py 4 | # 5 | # Copyright 2025 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import platform 13 | from pathlib import Path 14 | 15 | import pytest 16 | 17 | from b2sdk._internal.scan.exception import UnsupportedFilename 18 | from b2sdk._internal.scan.folder import LocalFolder 19 | 20 | 21 | @pytest.mark.skipif( 22 | platform.system() == 'Windows', 23 | reason="Windows doesn't allow / or \\ in filenames", 24 | ) 25 | class TestFolder: 26 | @pytest.fixture 27 | def root_path(self, tmp_path: Path): 28 | return tmp_path / 'dir' 29 | 30 | @pytest.fixture 31 | def folder(self, root_path: Path): 32 | return LocalFolder(str(root_path)) 33 | 34 | @pytest.mark.parametrize('file_path_str', ['dir/foo.txt', 'dir/foo/bar.txt', 'foo.txt']) 35 | def test_make_full_path(self, folder: LocalFolder, root_path: Path, file_path_str: str): 36 | file_path = root_path / file_path_str 37 | assert folder.make_full_path(str(file_path)) == str(root_path / file_path_str) 38 | 39 | @pytest.mark.parametrize('file_path_str', ['invalid/test.txt', 'dirinvalid.txt']) 40 | def test_make_full_path_invalid_prefix( 41 | self, folder: LocalFolder, tmp_path: Path, file_path_str: str 42 | ): 43 | file_path = tmp_path / file_path_str 44 | 45 | with pytest.raises(UnsupportedFilename): 46 | folder.make_full_path(str(file_path)) 47 | -------------------------------------------------------------------------------- /b2sdk/_internal/http_constants.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/http_constants.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import string 13 | 14 | # These constants are needed in different modules, so they are stored in this module, that 15 | # imports nothing, thus avoiding circular imports 16 | 17 | # https://www.backblaze.com/docs/cloud-storage-buckets#bucket-names 18 | BUCKET_NAME_CHARS = string.ascii_lowercase + string.digits + '-' 19 | BUCKET_NAME_CHARS_UNIQ = string.ascii_lowercase + string.digits + '-' 20 | BUCKET_NAME_LENGTH_RANGE = (6, 63) 21 | 22 | LIST_FILE_NAMES_MAX_LIMIT = 10000 # https://www.backblaze.com/b2/docs/b2_list_file_names.html 23 | 24 | FILE_INFO_HEADER_PREFIX = 'X-Bz-Info-' 25 | FILE_INFO_HEADER_PREFIX_LOWER = FILE_INFO_HEADER_PREFIX.lower() 26 | 27 | # Standard names for file info entries 28 | SRC_LAST_MODIFIED_MILLIS = 'src_last_modified_millis' 29 | 30 | # SHA-1 hash key for large files 31 | LARGE_FILE_SHA1 = 'large_file_sha1' 32 | 33 | # Special X-Bz-Content-Sha1 value to verify checksum at the end 34 | HEX_DIGITS_AT_END = 'hex_digits_at_end' 35 | 36 | # Identifying SSE_C keys 37 | SSE_C_KEY_ID_FILE_INFO_KEY_NAME = 'sse_c_key_id' 38 | SSE_C_KEY_ID_HEADER = FILE_INFO_HEADER_PREFIX + SSE_C_KEY_ID_FILE_INFO_KEY_NAME 39 | 40 | # Default part sizes 41 | MEGABYTE = 1000 * 1000 42 | GIGABYTE = 1000 * MEGABYTE 43 | DEFAULT_MIN_PART_SIZE = 5 * MEGABYTE 44 | DEFAULT_RECOMMENDED_UPLOAD_PART_SIZE = 100 * MEGABYTE 45 | DEFAULT_MAX_PART_SIZE = 5 * GIGABYTE 46 | -------------------------------------------------------------------------------- /test/unit/v0/test_file_metadata.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v0/test_file_metadata.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from ..test_base import TestBase 13 | from .deps import FileMetadata 14 | 15 | 16 | def snake_to_camel(name): 17 | camel = ''.join(s.title() for s in name.split('_')) 18 | return camel[:1].lower() + camel[1:] 19 | 20 | 21 | class TestFileMetadata(TestBase): 22 | KWARGS = { 23 | 'file_id': '4_deadbeaf3b3e38a957f100d1e_f1042665d79618ae7_d20200903_m194254_c000_v0001053_t0048', 24 | 'file_name': 'foo.txt', 25 | 'content_type': 'text/plain', 26 | 'content_length': '1', 27 | 'content_sha1': '4518012e1b365e504001dbc94120624f15b8bbd5', 28 | 'file_info': {}, 29 | } 30 | INFO_DICT = {snake_to_camel(k): v for k, v in KWARGS.items()} 31 | 32 | def test_verified_sha1(self): 33 | metadata = FileMetadata(**self.KWARGS) 34 | 35 | self.assertTrue(metadata.content_sha1_verified) 36 | self.assertEqual(metadata.as_info_dict(), self.INFO_DICT) 37 | 38 | def test_unverified_sha1(self): 39 | kwargs = self.KWARGS.copy() 40 | kwargs['content_sha1'] = 'unverified:' + kwargs['content_sha1'] 41 | info_dict = self.INFO_DICT.copy() 42 | info_dict['contentSha1'] = 'unverified:' + info_dict['contentSha1'] 43 | metadata = FileMetadata(**kwargs) 44 | 45 | self.assertFalse(metadata.content_sha1_verified) 46 | self.assertEqual(metadata.as_info_dict(), info_dict) 47 | -------------------------------------------------------------------------------- /test/unit/v1/test_file_metadata.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v1/test_file_metadata.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from ..test_base import TestBase 13 | from .deps import FileMetadata 14 | 15 | 16 | def snake_to_camel(name): 17 | camel = ''.join(s.title() for s in name.split('_')) 18 | return camel[:1].lower() + camel[1:] 19 | 20 | 21 | class TestFileMetadata(TestBase): 22 | KWARGS = { 23 | 'file_id': '4_deadbeaf3b3e38a957f100d1e_f1042665d79618ae7_d20200903_m194254_c000_v0001053_t0048', 24 | 'file_name': 'foo.txt', 25 | 'content_type': 'text/plain', 26 | 'content_length': '1', 27 | 'content_sha1': '4518012e1b365e504001dbc94120624f15b8bbd5', 28 | 'file_info': {}, 29 | } 30 | INFO_DICT = {snake_to_camel(k): v for k, v in KWARGS.items()} 31 | 32 | def test_verified_sha1(self): 33 | metadata = FileMetadata(**self.KWARGS) 34 | 35 | self.assertTrue(metadata.content_sha1_verified) 36 | self.assertEqual(metadata.as_info_dict(), self.INFO_DICT) 37 | 38 | def test_unverified_sha1(self): 39 | kwargs = self.KWARGS.copy() 40 | kwargs['content_sha1'] = 'unverified:' + kwargs['content_sha1'] 41 | info_dict = self.INFO_DICT.copy() 42 | info_dict['contentSha1'] = 'unverified:' + info_dict['contentSha1'] 43 | metadata = FileMetadata(**kwargs) 44 | 45 | self.assertFalse(metadata.content_sha1_verified) 46 | self.assertEqual(metadata.as_info_dict(), info_dict) 47 | -------------------------------------------------------------------------------- /test/unit/replication/conftest.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/replication/conftest.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import pytest 13 | from apiver_deps import ( 14 | Bucket, 15 | ReplicationConfiguration, 16 | ReplicationMonitor, 17 | ReplicationRule, 18 | ) 19 | 20 | 21 | @pytest.fixture 22 | def destination_bucket(b2api) -> Bucket: 23 | return b2api.create_bucket('destination-bucket', 'allPublic') 24 | 25 | 26 | @pytest.fixture 27 | def source_bucket(b2api, destination_bucket) -> Bucket: 28 | bucket = b2api.create_bucket('source-bucket', 'allPublic') 29 | 30 | bucket.replication = ReplicationConfiguration( 31 | rules=[ 32 | ReplicationRule( 33 | destination_bucket_id=destination_bucket.id_, 34 | name='name', 35 | file_name_prefix='folder/', 36 | ), 37 | ], 38 | source_key_id='hoho|trololo', 39 | ) 40 | 41 | return bucket 42 | 43 | 44 | @pytest.fixture 45 | def test_file(tmpdir) -> str: 46 | file = tmpdir.join('test.txt') 47 | file.write('whatever') 48 | return file 49 | 50 | 51 | @pytest.fixture 52 | def test_file_reversed(tmpdir) -> str: 53 | file = tmpdir.join('test-reversed.txt') 54 | file.write('revetahw') 55 | return file 56 | 57 | 58 | @pytest.fixture 59 | def monitor(source_bucket) -> ReplicationMonitor: 60 | return ReplicationMonitor( 61 | source_bucket, 62 | rule=source_bucket.replication.rules[0], 63 | ) 64 | -------------------------------------------------------------------------------- /b2sdk/_internal/testing/helpers/base.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/testing/helpers/base.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import pytest 13 | 14 | from b2sdk._internal.testing.helpers.bucket_manager import BucketManager 15 | from b2sdk.v2 import B2Api 16 | 17 | 18 | @pytest.mark.usefixtures('cls_setup') 19 | class IntegrationTestBase: 20 | b2_api: B2Api 21 | this_run_bucket_name_prefix: str 22 | bucket_manager: BucketManager 23 | 24 | @pytest.fixture(autouse=True, scope='class') 25 | def cls_setup(self, request, b2_api, b2_auth_data, bucket_name_prefix, bucket_manager): 26 | cls = request.cls 27 | cls.b2_auth_data = b2_auth_data 28 | cls.this_run_bucket_name_prefix = bucket_name_prefix 29 | cls.bucket_manager = bucket_manager 30 | cls.b2_api = b2_api 31 | cls.info = b2_api.account_info 32 | 33 | @pytest.fixture(autouse=True) 34 | def setup_method(self): 35 | self.buckets_created = [] 36 | yield 37 | for bucket in self.buckets_created: 38 | self.bucket_manager.clean_bucket(bucket) 39 | 40 | def write_zeros(self, file, number): 41 | line = b'0' * 1000 + b'\n' 42 | line_len = len(line) 43 | written = 0 44 | while written <= number: 45 | file.write(line) 46 | written += line_len 47 | 48 | def create_bucket(self): 49 | bucket = self.bucket_manager.create_bucket() 50 | self.buckets_created.append(bucket) 51 | return bucket 52 | -------------------------------------------------------------------------------- /b2sdk/_internal/utils/docs.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/utils/docs.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | 13 | class MissingDocURL(Exception): 14 | pass 15 | 16 | 17 | def ensure_b2sdk_doc_urls(cls: type): 18 | """ 19 | Decorator to indicate (and verify) that class has external documentation URLs. 20 | 21 | Used for to validate that all classes have external documentation URLs properly defined. 22 | """ 23 | urls = get_b2sdk_doc_urls(cls) 24 | if not urls: 25 | raise MissingDocURL(f'No documentation URLs found for {cls.__name__}') 26 | return cls 27 | 28 | 29 | def get_b2sdk_doc_urls(type_: type) -> dict[str, str]: 30 | """ 31 | Get the external documentation URLs for a b2sdk class. 32 | 33 | Non-b2sdk classes are not, and will not be supported. 34 | 35 | :param type_: the class to get the documentation URLs for 36 | :return: a dictionary mapping link names to URLs 37 | """ 38 | docstring = type_.__doc__ 39 | if not docstring: 40 | return {} 41 | return _extract_restructedtext_links(docstring) 42 | 43 | 44 | _rest_link_prefix = '.. _' 45 | 46 | 47 | def _extract_restructedtext_links(docstring: str) -> dict[str, str]: 48 | links = {} 49 | for line in docstring.splitlines(): 50 | line = line.strip() 51 | if line.startswith(_rest_link_prefix): 52 | name, url = line[len(_rest_link_prefix) :].split(': ', 1) 53 | if name and url: 54 | links[name] = url 55 | return links 56 | -------------------------------------------------------------------------------- /test/unit/utils/test_filesystem.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/utils/test_filesystem.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | import os 11 | import pathlib 12 | import platform 13 | 14 | import pytest 15 | from apiver_deps import ( 16 | STDOUT_FILEPATH, 17 | points_to_fifo, 18 | points_to_stdout, 19 | ) 20 | 21 | EXPECTED_STDOUT_PATH = pathlib.Path('CON' if platform.system() == 'Windows' else '/dev/stdout') 22 | 23 | 24 | class TestPointsToFifo: 25 | @pytest.mark.skipif(platform.system() == 'Windows', reason='no os.mkfifo() on Windows') 26 | def test_fifo_path(self, tmp_path): 27 | fifo_path = tmp_path / 'fifo' 28 | os.mkfifo(fifo_path) 29 | assert points_to_fifo(fifo_path) is True 30 | 31 | def test_non_fifo_path(self, tmp_path): 32 | path = tmp_path / 'subdir' 33 | path.mkdir(parents=True) 34 | assert points_to_fifo(path) is False 35 | 36 | def test_non_existent_path(self, tmp_path): 37 | path = tmp_path / 'file.txt' 38 | assert points_to_fifo(path) is False 39 | 40 | 41 | class TestPointsToStdout: 42 | def test_stdout_path(self): 43 | assert points_to_stdout(EXPECTED_STDOUT_PATH) is True 44 | assert points_to_stdout(STDOUT_FILEPATH) is True 45 | 46 | def test_non_stdout_path(self, tmp_path): 47 | path = tmp_path / 'file.txt' 48 | path.touch() 49 | assert points_to_stdout(path) is False 50 | 51 | def test_non_existent_stdout_path(self, tmp_path): 52 | path = tmp_path / 'file.txt' 53 | assert points_to_stdout(path) is False 54 | -------------------------------------------------------------------------------- /b2sdk/_internal/transfer/outbound/progress_reporter.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/transfer/outbound/progress_reporter.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk._internal.progress import AbstractProgressListener 13 | 14 | 15 | class PartProgressReporter(AbstractProgressListener): 16 | """ 17 | An adapter that listens to the progress of upload a part and 18 | gives the information to a :py:class:`b2sdk._internal.transfer.outbound.large_file_upload_state.LargeFileUploadState`. 19 | 20 | Accepts absolute bytes_completed from the uploader, and reports 21 | deltas to the :py:class:`b2sdk._internal.transfer.outbound.large_file_upload_state.LargeFileUploadState`. The bytes_completed for the 22 | part will drop back to 0 on a retry, which will result in a 23 | negative delta. 24 | """ 25 | 26 | def __init__(self, large_file_upload_state, *args, **kwargs): 27 | """ 28 | :param b2sdk._internal.transfer.outbound.large_file_upload_state.LargeFileUploadState large_file_upload_state: object to relay the progress to 29 | """ 30 | super().__init__(*args, **kwargs) 31 | self.large_file_upload_state = large_file_upload_state 32 | self.prev_byte_count = 0 33 | 34 | def bytes_completed(self, byte_count): 35 | self.large_file_upload_state.update_part_bytes(byte_count - self.prev_byte_count) 36 | self.prev_byte_count = byte_count 37 | 38 | def close(self): 39 | pass 40 | 41 | def set_total_bytes(self, total_byte_count): 42 | pass 43 | -------------------------------------------------------------------------------- /b2sdk/_internal/utils/escape.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/utils/escape.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | 11 | from __future__ import annotations 12 | 13 | import re 14 | import shlex 15 | 16 | # skip newline, tab 17 | UNPRINTABLE_PATTERN = re.compile(r'[\x00-\x08\x0e-\x1f\x7f-\x9f]') 18 | 19 | 20 | def unprintable_to_hex(s: str) -> str: 21 | """ 22 | Replace unprintable chars in string with a hex representation. 23 | 24 | :param s: an arbitrary string, possibly with unprintable characters. 25 | :return: the string, with unprintable characters changed to hex (e.g., "\x07") 26 | """ 27 | 28 | def hexify(match): 29 | return rf'\x{ord(match.group()):02x}' 30 | 31 | if s: 32 | return UNPRINTABLE_PATTERN.sub(hexify, s) 33 | return s 34 | 35 | 36 | def escape_control_chars(s: str) -> str: 37 | """ 38 | Replace unprintable chars in string with a hex representation AND shell quotes the string. 39 | 40 | :param s: an arbitrary string, possibly with unprintable characters. 41 | :return: the string, with unprintable characters changed to hex (e.g., "\x07") 42 | """ 43 | if s: 44 | return shlex.quote(unprintable_to_hex(s)) 45 | return s 46 | 47 | 48 | def substitute_control_chars(s: str) -> tuple[str, bool]: 49 | """ 50 | Replace unprintable chars in string with � unicode char 51 | 52 | :param s: an arbitrary string, possibly with unprintable characters. 53 | :return: tuple of the string with � replacements made and boolean indicated if chars were replaced 54 | """ 55 | new_value = UNPRINTABLE_PATTERN.sub('�', s) 56 | return new_value, new_value != s 57 | -------------------------------------------------------------------------------- /test/unit/v2/test_bucket.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v2/test_bucket.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from unittest.mock import Mock 13 | 14 | import pytest 15 | 16 | from b2sdk import v3 17 | from b2sdk.v2 import B2Api, Bucket 18 | from test.helpers import patch_bind_params 19 | 20 | 21 | @pytest.fixture 22 | def dummy_bucket(): 23 | return Bucket(Mock(spec=B2Api), 'bucket_id', 'bucket_name') 24 | 25 | 26 | def test_bucket__upload_file__supports_file_infos(dummy_bucket, file_info): 27 | """Test v2.Bucket.upload_file support of deprecated file_infos param""" 28 | with patch_bind_params(v3.Bucket, 'upload_local_file') as mock_method, pytest.warns( 29 | DeprecationWarning, match=r'deprecated argument' 30 | ): 31 | dummy_bucket.upload_local_file( 32 | 'filename', 33 | 'filename', 34 | file_infos=file_info, 35 | ) 36 | assert mock_method.get_bound_call_args()['file_info'] == file_info 37 | assert 'file_infos' not in mock_method.call_args[1] 38 | 39 | 40 | def test_bucket__upload_bytes__supports_file_infos(dummy_bucket, file_info): 41 | """Test v2.Bucket.upload_bytes support of deprecated file_infos param""" 42 | with patch_bind_params(dummy_bucket, 'upload') as mock_method, pytest.warns( 43 | DeprecationWarning, match=r'deprecated argument' 44 | ): 45 | dummy_bucket.upload_bytes( 46 | b'data', 47 | 'filename', 48 | file_infos=file_info, 49 | ) 50 | assert mock_method.get_bound_call_args()['file_info'] == file_info 51 | assert 'file_infos' not in mock_method.call_args[1] 52 | -------------------------------------------------------------------------------- /test/unit/sync/test_sync_report.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/sync/test_sync_report.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from unittest.mock import MagicMock 13 | 14 | import pytest 15 | from apiver_deps import SyncReport 16 | 17 | 18 | class TestSyncReport: 19 | def test_bad_terminal(self): 20 | stdout = MagicMock() 21 | stdout.write = MagicMock( 22 | side_effect=[ 23 | UnicodeEncodeError('codec', 'foo', 100, 105, 'artificial UnicodeEncodeError') 24 | ] 25 | + list(range(25)) 26 | ) 27 | sync_report = SyncReport(stdout, False) 28 | sync_report.print_completion('transferred: 123.txt') 29 | 30 | @pytest.mark.apiver(to_ver=1) 31 | def test_legacy_methods(self): 32 | stdout = MagicMock() 33 | sync_report = SyncReport(stdout, False) 34 | 35 | assert not sync_report.total_done 36 | assert not sync_report.local_done 37 | assert 0 == sync_report.total_count 38 | assert 0 == sync_report.local_file_count 39 | 40 | sync_report.local_done = True 41 | assert sync_report.local_done 42 | assert sync_report.total_done 43 | 44 | sync_report.local_file_count = 8 45 | assert 8 == sync_report.local_file_count 46 | assert 8 == sync_report.total_count 47 | 48 | sync_report.update_local(7) 49 | assert 15 == sync_report.total_count 50 | assert 15 == sync_report.local_file_count 51 | 52 | sync_report = SyncReport(stdout, False) 53 | assert not sync_report.total_done 54 | sync_report.end_local() 55 | assert sync_report.total_done 56 | -------------------------------------------------------------------------------- /b2sdk/_internal/api_config.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/api_config.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from typing import Callable 13 | 14 | import requests 15 | 16 | from .raw_api import AbstractRawApi, B2RawHTTPApi 17 | 18 | 19 | class B2HttpApiConfig: 20 | DEFAULT_RAW_API_CLASS = B2RawHTTPApi 21 | 22 | def __init__( 23 | self, 24 | http_session_factory: Callable[[], requests.Session] = requests.Session, 25 | install_clock_skew_hook: bool = True, 26 | user_agent_append: str | None = None, 27 | _raw_api_class: type[AbstractRawApi] | None = None, 28 | decode_content: bool = False, 29 | ): 30 | """ 31 | A structure with params to be passed to low level API. 32 | 33 | :param http_session_factory: a callable that returns a requests.Session object (or a compatible one) 34 | :param install_clock_skew_hook: if True, install a clock skew hook 35 | :param user_agent_append: if provided, the string will be appended to the User-Agent 36 | :param _raw_api_class: AbstractRawApi-compliant class 37 | :param decode_content: If true, the underlying http backend will try to decode encoded files when downloading, 38 | based on the response headers 39 | """ 40 | self.http_session_factory = http_session_factory 41 | self.install_clock_skew_hook = install_clock_skew_hook 42 | self.user_agent_append = user_agent_append 43 | self.raw_api_class = _raw_api_class or self.DEFAULT_RAW_API_CLASS 44 | self.decode_content = decode_content 45 | 46 | 47 | DEFAULT_HTTP_API_CONFIG = B2HttpApiConfig() 48 | -------------------------------------------------------------------------------- /test/unit/stream/test_progress.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/stream/test_progress.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | import io 11 | from unittest.mock import Mock 12 | 13 | from apiver_deps import ReadingStreamWithProgress 14 | 15 | 16 | def test_reading_stream_with_progress(tmp_path): 17 | stream = io.BytesIO(b'1234567890') 18 | progress_listener = Mock() 19 | with ReadingStreamWithProgress(stream, progress_listener=progress_listener) as wrapped_stream: 20 | assert wrapped_stream.read(1) == b'1' 21 | assert wrapped_stream.read(2) == b'23' 22 | assert wrapped_stream.read(3) == b'456' 23 | 24 | assert progress_listener.bytes_completed.call_count == 3 25 | assert wrapped_stream.bytes_completed == 6 26 | 27 | assert not stream.closed 28 | 29 | 30 | def test_reading_stream_with_progress__not_closing_wrapped_stream(tmp_path): 31 | stream = io.BytesIO(b'1234567890') 32 | progress_listener = Mock() 33 | with ReadingStreamWithProgress(stream, progress_listener=progress_listener) as wrapped_stream: 34 | assert wrapped_stream.read() 35 | 36 | assert not stream.closed 37 | 38 | 39 | def test_reading_stream_with_progress__closed_proxy(tmp_path): 40 | """ 41 | Test that the wrapped stream is closed when the original stream is closed. 42 | 43 | This is important for Python 3.13+ to prevent: 44 | 'Exception ignored in: ' 45 | messages. 46 | """ 47 | stream = io.BytesIO(b'1234567890') 48 | progress_listener = Mock() 49 | wrapped_stream = ReadingStreamWithProgress(stream, progress_listener=progress_listener) 50 | 51 | assert not stream.closed 52 | stream.close() 53 | assert wrapped_stream.closed 54 | -------------------------------------------------------------------------------- /b2sdk/_internal/transfer/outbound/outbound_source.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/transfer/outbound/outbound_source.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from abc import ABCMeta, abstractmethod 13 | 14 | from b2sdk._internal.utils import Sha1HexDigest 15 | 16 | 17 | class OutboundTransferSource(metaclass=ABCMeta): 18 | """Abstract class for defining outbound transfer sources. 19 | 20 | Supported outbound transfer sources are: 21 | 22 | * :class:`b2sdk.v2.CopySource` 23 | * :class:`b2sdk.v2.UploadSourceBytes` 24 | * :class:`b2sdk.v2.UploadSourceLocalFile` 25 | * :class:`b2sdk.v2.UploadSourceLocalFileRange` 26 | * :class:`b2sdk.v2.UploadSourceStream` 27 | * :class:`b2sdk.v2.UploadSourceStreamRange` 28 | 29 | """ 30 | 31 | @abstractmethod 32 | def get_content_length(self) -> int: 33 | """ 34 | Returns the number of bytes of data in the file. 35 | """ 36 | 37 | @abstractmethod 38 | def get_content_sha1(self) -> Sha1HexDigest | None: 39 | """ 40 | Return a 40-character string containing the hex SHA1 checksum, which can be used as the `large_file_sha1` entry. 41 | 42 | This method is only used if a large file is constructed from only a single source. If that source's hash is known, 43 | the result file's SHA1 checksum will be the same and can be copied. 44 | 45 | If the source's sha1 is unknown and can't be calculated, `None` is returned. 46 | """ 47 | 48 | @abstractmethod 49 | def is_upload(self) -> bool: 50 | """ 51 | Returns True if outbound source is an upload source. 52 | """ 53 | 54 | @abstractmethod 55 | def is_copy(self) -> bool: 56 | """ 57 | Returns True if outbound source is a copy source. 58 | """ 59 | -------------------------------------------------------------------------------- /test/unit/fixtures/raw_api.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/fixtures/raw_api.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from copy import copy 13 | 14 | import pytest 15 | from apiver_deps import ALL_CAPABILITIES, B2RawHTTPApi 16 | 17 | 18 | @pytest.fixture 19 | def fake_b2_raw_api_responses(apiver_int): 20 | capabilities = copy(ALL_CAPABILITIES) 21 | namePrefix = None 22 | 23 | storage_api = { 24 | 'downloadUrl': 'https://f000.backblazeb2.xyz:8180', 25 | 'absoluteMinimumPartSize': 5000000, 26 | 'recommendedPartSize': 100000000, 27 | 'apiUrl': 'https://api000.backblazeb2.xyz:8180', 28 | 's3ApiUrl': 'https://s3.us-west-000.backblazeb2.xyz:8180', 29 | } 30 | 31 | if apiver_int < 3: 32 | storage_api.update( 33 | { 34 | 'capabilities': capabilities, 35 | 'namePrefix': namePrefix, 36 | 'bucketId': None, 37 | 'bucketName': None, 38 | } 39 | ) 40 | else: 41 | storage_api['allowed'] = { 42 | 'buckets': None, 43 | 'capabilities': capabilities, 44 | 'namePrefix': namePrefix, 45 | } 46 | 47 | return { 48 | 'authorize_account': { 49 | 'accountId': '6012deadbeef', 50 | 'apiInfo': { 51 | 'groupsApi': {}, 52 | 'storageApi': storage_api, 53 | }, 54 | 'authorizationToken': '4_1111111111111111111111111_11111111_111111_1111_1111111111111_1111_11111111=', 55 | } 56 | } 57 | 58 | 59 | @pytest.fixture 60 | def fake_b2_raw_api(mocker, fake_b2http, fake_b2_raw_api_responses): 61 | raw_api = mocker.MagicMock(name='FakeB2RawHTTPApi', spec=B2RawHTTPApi) 62 | raw_api.b2_http = fake_b2http 63 | raw_api.authorize_account.return_value = fake_b2_raw_api_responses['authorize_account'] 64 | return raw_api 65 | -------------------------------------------------------------------------------- /b2sdk/v1/b2http.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v1/b2http.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import requests 13 | 14 | from b2sdk import v2 15 | 16 | 17 | # Overridden to retain old-style __init__ signature 18 | class B2Http(v2.B2Http): 19 | """ 20 | A wrapper for the requests module. Provides the operations 21 | needed to access B2, and handles retrying when the returned 22 | status is 503 Service Unavailable, 429 Too Many Requests, etc. 23 | 24 | The operations supported are: 25 | 26 | - post_json_return_json 27 | - post_content_return_json 28 | - get_content 29 | 30 | The methods that return JSON either return a Python dict or 31 | raise a subclass of B2Error. They can be used like this: 32 | 33 | .. code-block:: python 34 | 35 | try: 36 | response_dict = b2_http.post_json_return_json(url, headers, params) 37 | ... 38 | except B2Error as e: 39 | ... 40 | 41 | """ 42 | 43 | # timeout for HTTP GET/POST requests 44 | TIMEOUT = 1200 # 20 minutes as server-side copy can take time 45 | 46 | def __init__(self, requests_module=None, install_clock_skew_hook=True, user_agent_append=None): 47 | """ 48 | Initialize with a reference to the requests module, which makes 49 | it easy to mock for testing. 50 | 51 | :param requests_module: a reference to requests module 52 | :param bool install_clock_skew_hook: if True, install a clock skew hook 53 | :param str user_agent_append: if provided, the string will be appended to the User-Agent 54 | """ 55 | super().__init__( 56 | v2.B2HttpApiConfig( 57 | http_session_factory=(requests_module or requests).Session, 58 | install_clock_skew_hook=install_clock_skew_hook, 59 | user_agent_append=user_agent_append, 60 | ) 61 | ) 62 | -------------------------------------------------------------------------------- /b2sdk/v2/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v2/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk.v3 import * # noqa 13 | from b2sdk.v3 import parse_folder as parse_sync_folder 14 | from b2sdk.v3 import AbstractPath as AbstractSyncPath 15 | from b2sdk.v3 import LocalPath as LocalSyncPath 16 | from b2sdk._internal.utils.escape import ( 17 | unprintable_to_hex, 18 | escape_control_chars, 19 | substitute_control_chars, 20 | ) 21 | 22 | from .account_info import AbstractAccountInfo 23 | from .account_info import InMemoryAccountInfo 24 | from .account_info import SqliteAccountInfo 25 | from .account_info import StubAccountInfo 26 | from .account_info import UrlPoolAccountInfo 27 | from .api import B2Api, Services 28 | from .b2http import B2Http 29 | from .api_config import B2HttpApiConfig, DEFAULT_HTTP_API_CONFIG 30 | from .bucket import Bucket, BucketFactory 31 | from .session import B2Session 32 | from .sync import B2SyncPath 33 | from .transfer import DownloadManager, UploadManager 34 | 35 | # replication 36 | 37 | from .replication.setup import ReplicationSetupHelper 38 | 39 | # data classes 40 | 41 | from .application_key import ApplicationKey 42 | from .application_key import BaseApplicationKey 43 | from .application_key import FullApplicationKey 44 | 45 | # utils 46 | 47 | from .version_utils import rename_argument, rename_function 48 | from .utils import TempDir 49 | 50 | # raw_simulator 51 | 52 | from .raw_simulator import BucketSimulator 53 | from .raw_simulator import RawSimulator 54 | from .raw_simulator import KeySimulator 55 | 56 | # raw_api 57 | 58 | from .raw_api import AbstractRawApi 59 | from .raw_api import B2RawHTTPApi 60 | 61 | # file_version 62 | 63 | from .file_version import FileVersion 64 | from .file_version import FileVersionFactory 65 | 66 | # large_file 67 | 68 | from .large_file import LargeFileServices 69 | from .large_file import UnfinishedLargeFile 70 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Delivery 2 | 3 | on: 4 | push: 5 | tags: ['v*'] # push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | env: 8 | PYTHON_DEFAULT_VERSION: "3.12" 9 | 10 | jobs: 11 | deploy: 12 | env: 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | B2_PYPI_PASSWORD: ${{ secrets.B2_PYPI_PASSWORD }} 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Determine if pre-release 18 | id: prerelease_check 19 | run: | 20 | export IS_PRERELEASE=$([[ ${{ github.ref }} =~ [^0-9]$ ]] && echo true || echo false) 21 | echo "prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT 22 | - uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | - name: Set up Python ${{ env.PYTHON_DEFAULT_VERSION }} 26 | uses: actions/setup-python@v6 27 | with: 28 | python-version: ${{ env.PYTHON_DEFAULT_VERSION }} 29 | - name: Display Python version 30 | run: python -c "import sys; print(sys.version)" 31 | - name: Install dependencies 32 | run: python -m pip install --upgrade nox pdm 33 | - name: Build the distribution 34 | id: build 35 | run: nox -vs build 36 | - name: Read the Changelog 37 | id: read-changelog 38 | uses: mindsers/changelog-reader-action@v2 39 | with: 40 | version: ${{ steps.build.outputs.version }} 41 | - name: Create GitHub release and upload the distribution 42 | id: create-release 43 | uses: softprops/action-gh-release@v2 44 | with: 45 | name: ${{ steps.build.outputs.version }} 46 | body: ${{ steps.read-changelog.outputs.changes }} 47 | draft: ${{ env.ACTIONS_STEP_DEBUG == 'true' }} 48 | prerelease: ${{ steps.prerelease_check.outputs.prerelease }} 49 | files: ${{ steps.build.outputs.asset_path }} 50 | - name: Upload the distribution to PyPI 51 | if: ${{ env.B2_PYPI_PASSWORD != '' && steps.prerelease_check.outputs.prerelease == 'false' }} 52 | uses: pypa/gh-action-pypi-publish@release/v1 53 | with: 54 | user: __token__ 55 | password: ${{ secrets.B2_PYPI_PASSWORD }} 56 | -------------------------------------------------------------------------------- /b2sdk/_internal/testing/fixtures/buckets.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/testing/fixtures/buckets.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import secrets 13 | 14 | import pytest 15 | 16 | from b2sdk._internal.testing.helpers.bucket_manager import BucketManager 17 | from b2sdk._internal.testing.helpers.buckets import ( 18 | GENERAL_BUCKET_NAME_PREFIX, 19 | get_bucket_name_prefix, 20 | ) 21 | 22 | 23 | def pytest_addoption(parser): 24 | """Add a flag for not cleaning up old buckets""" 25 | parser.addoption( 26 | '--dont-cleanup-old-buckets', 27 | action='store_true', 28 | default=False, 29 | ) 30 | 31 | 32 | @pytest.fixture(scope='session') 33 | def dont_cleanup_old_buckets(request): 34 | return request.config.getoption('--dont-cleanup-old-buckets') 35 | 36 | 37 | @pytest.fixture(scope='session') 38 | def general_bucket_name_prefix(): 39 | return GENERAL_BUCKET_NAME_PREFIX 40 | 41 | 42 | @pytest.fixture(scope='session') 43 | def bucket_name_prefix(general_bucket_name_prefix): 44 | return get_bucket_name_prefix(8, general_bucket_name_prefix) 45 | 46 | 47 | @pytest.fixture(scope='session') 48 | def bucket_manager( 49 | bucket_name_prefix, general_bucket_name_prefix, dont_cleanup_old_buckets, _b2_api 50 | ): 51 | manager = BucketManager( 52 | dont_cleanup_old_buckets, 53 | _b2_api, 54 | current_run_prefix=bucket_name_prefix, 55 | general_prefix=general_bucket_name_prefix, 56 | ) 57 | yield manager 58 | manager.clean_buckets() 59 | 60 | 61 | @pytest.fixture 62 | def bucket(bucket_name_prefix, bucket_manager): 63 | bucket = bucket_manager.create_bucket() 64 | yield bucket 65 | bucket_manager.clean_bucket(bucket) 66 | 67 | 68 | @pytest.fixture 69 | def b2_subfolder(bucket, request): 70 | subfolder_name = f'{request.node.name}_{secrets.token_urlsafe(4)}' 71 | return f'b2://{bucket.name}/{subfolder_name}' 72 | -------------------------------------------------------------------------------- /test/integration/test_sync.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/integration/test_sync.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import io 13 | import time 14 | 15 | import pytest 16 | 17 | from b2sdk.v3 import ( 18 | CompareVersionMode, 19 | NewerFileSyncMode, 20 | Synchronizer, 21 | SyncReport, 22 | parse_folder, 23 | ) 24 | 25 | 26 | @pytest.fixture 27 | def local_folder_with_files(tmp_path): 28 | folder = tmp_path / 'test' 29 | folder.mkdir() 30 | (folder / 'a').mkdir() 31 | (folder / 'a' / 'foo').write_bytes(b'foo') 32 | # space in the name is important as it influences lexicographical sorting used by B2 33 | (folder / 'a b').mkdir() 34 | (folder / 'a b' / 'bar').write_bytes(b'bar') 35 | return folder 36 | 37 | 38 | def test_sync_folder(b2_api, local_folder_with_files, b2_subfolder): 39 | source_folder = parse_folder(str(local_folder_with_files), b2_api) 40 | dest_folder = parse_folder(b2_subfolder, b2_api) 41 | 42 | synchronizer = Synchronizer( 43 | max_workers=10, 44 | newer_file_mode=NewerFileSyncMode.REPLACE, 45 | compare_version_mode=CompareVersionMode.MODTIME, 46 | compare_threshold=10, # ms 47 | ) 48 | 49 | def sync_and_report(): 50 | buf = io.StringIO() 51 | reporter = SyncReport(buf, no_progress=True) 52 | with reporter: 53 | synchronizer.sync_folders( 54 | source_folder=source_folder, 55 | dest_folder=dest_folder, 56 | now_millis=int(1000 * time.time()), 57 | reporter=reporter, 58 | ) 59 | return reporter 60 | 61 | report = sync_and_report() 62 | assert report.total_transfer_files == 2 63 | assert report.total_transfer_bytes == 6 64 | 65 | second_sync_report = sync_and_report() 66 | assert second_sync_report.total_transfer_files == 0 67 | assert second_sync_report.total_transfer_bytes == 0 68 | -------------------------------------------------------------------------------- /doc/source/api_reference.rst: -------------------------------------------------------------------------------- 1 | .. hint:: Use :doc:`quick_start` to quickly jump to examples 2 | 3 | ######################## 4 | API Reference 5 | ######################## 6 | 7 | Interface types 8 | ======================= 9 | 10 | **b2sdk** API is divided into two parts, :ref:`public ` and :ref:`internal `. Please pay attention to which interface type you use. 11 | 12 | 13 | .. tip:: 14 | :ref:`Pinning versions ` properly ensures the stability of your application. 15 | 16 | 17 | .. _api_public: 18 | 19 | Public API 20 | ======================== 21 | 22 | .. toctree:: 23 | api/application_key 24 | api/account_info 25 | api/cache 26 | api/api 27 | api/exception 28 | api/bucket 29 | api/file_lock 30 | api/data_classes 31 | api/downloaded_file 32 | api/enums 33 | api/progress 34 | api/sync 35 | api/utils 36 | api/transfer/emerge/write_intent 37 | api/transfer/outbound/outbound_source 38 | api/encryption/setting 39 | api/encryption/types 40 | 41 | .. _api_internal: 42 | 43 | Internal API 44 | ======================== 45 | 46 | .. note:: See :ref:`Internal interface ` chapter to learn when and how to safely use the Internal API 47 | 48 | .. toctree:: 49 | api/internal/session 50 | api/internal/raw_api 51 | api/internal/b2http 52 | api/internal/requests 53 | api/internal/utils 54 | api/internal/cache 55 | api/internal/stream/chained 56 | api/internal/stream/hashing 57 | api/internal/stream/progress 58 | api/internal/stream/range 59 | api/internal/stream/wrapper 60 | api/internal/scan/folder_parser 61 | api/internal/scan/folder 62 | api/internal/scan/path 63 | api/internal/scan/policies 64 | api/internal/scan/scan 65 | api/internal/sync/action 66 | api/internal/sync/exception 67 | api/internal/sync/policy 68 | api/internal/sync/policy_manager 69 | api/internal/sync/sync 70 | api/internal/transfer/inbound/downloader/abstract 71 | api/internal/transfer/inbound/downloader/parallel 72 | api/internal/transfer/inbound/downloader/simple 73 | api/internal/transfer/inbound/download_manager 74 | api/internal/transfer/outbound/upload_source 75 | api/internal/raw_simulator 76 | -------------------------------------------------------------------------------- /b2sdk/_internal/scan/folder_parser.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/scan/folder_parser.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from .exception import InvalidArgument 13 | from .folder import B2Folder, LocalFolder 14 | 15 | 16 | def parse_folder(folder_name, api, local_folder_class=LocalFolder, b2_folder_class=B2Folder): 17 | """ 18 | Take either a local path, or a B2 path, and returns a Folder 19 | object for it. 20 | 21 | B2 paths look like: b2://bucketName/path/name. The '//' is optional. 22 | 23 | Anything else is treated like a local folder. 24 | 25 | :param folder_name: a name of the folder, either local or remote 26 | :type folder_name: str 27 | :param api: an API object 28 | :type api: :class:`~b2sdk.v2.B2Api` 29 | :param local_folder_class: class to handle local folders 30 | :type local_folder_class: `~b2sdk.v2.AbstractFolder` 31 | :param b2_folder_class: class to handle B2 folders 32 | :type b2_folder_class: `~b2sdk.v2.AbstractFolder` 33 | """ 34 | if folder_name.startswith('b2://'): 35 | return _parse_bucket_and_folder(folder_name[5:], api, b2_folder_class) 36 | elif folder_name.startswith('b2:') and folder_name[3].isalnum(): 37 | return _parse_bucket_and_folder(folder_name[3:], api, b2_folder_class) 38 | else: 39 | return local_folder_class(folder_name) 40 | 41 | 42 | def _parse_bucket_and_folder(bucket_and_path, api, b2_folder_class): 43 | """ 44 | Turn 'my-bucket/foo' into B2Folder(my-bucket, foo). 45 | """ 46 | if '//' in bucket_and_path: 47 | raise InvalidArgument('folder_name', "'//' not allowed in path names") 48 | if '/' not in bucket_and_path: 49 | bucket_name = bucket_and_path 50 | folder_name = '' 51 | else: 52 | (bucket_name, folder_name) = bucket_and_path.split('/', 1) 53 | if folder_name.endswith('/'): 54 | folder_name = folder_name[:-1] 55 | return b2_folder_class(bucket_name, folder_name, api) 56 | -------------------------------------------------------------------------------- /b2sdk/_internal/filter.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/filter.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import fnmatch 13 | from dataclasses import dataclass 14 | from enum import Enum 15 | from typing import Sequence 16 | 17 | 18 | class FilterType(Enum): 19 | INCLUDE = 'include' 20 | EXCLUDE = 'exclude' 21 | 22 | 23 | @dataclass 24 | class Filter: 25 | type: FilterType 26 | pattern: str 27 | 28 | @classmethod 29 | def include(cls, pattern: str) -> Filter: 30 | return cls(type=FilterType.INCLUDE, pattern=pattern) 31 | 32 | @classmethod 33 | def exclude(cls, pattern: str) -> Filter: 34 | return cls(type=FilterType.EXCLUDE, pattern=pattern) 35 | 36 | 37 | class FilterMatcher: 38 | """ 39 | Holds a list of filters and matches a string (i.e. file name) against them. 40 | 41 | The order of filters matters. The *last* matching filter decides whether 42 | the string is included or excluded. If no filter matches, the string is 43 | included by default. 44 | 45 | If the given list of filters contains only INCLUDE filters, then it is 46 | assumed that all files are excluded by default. In this case, an additional 47 | EXCLUDE filter is prepended to the list. 48 | 49 | :param filters: list of filters 50 | """ 51 | 52 | def __init__(self, filters: Sequence[Filter]): 53 | if filters and all(filter_.type == FilterType.INCLUDE for filter_ in filters): 54 | filters = [Filter(type=FilterType.EXCLUDE, pattern='*'), *filters] 55 | 56 | self.filters = filters 57 | 58 | def match(self, s: str) -> bool: 59 | include_file = True 60 | for filter_ in self.filters: 61 | matched = fnmatch.fnmatchcase(s, filter_.pattern) 62 | if matched and filter_.type == FilterType.INCLUDE: 63 | include_file = True 64 | elif matched and filter_.type == FilterType.EXCLUDE: 65 | include_file = False 66 | 67 | return include_file 68 | -------------------------------------------------------------------------------- /test/integration/test_bucket.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/integration/test_bucket.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | import pytest 11 | 12 | from test.helpers import assert_dict_equal_ignore_extra 13 | 14 | 15 | def test_bucket_notification_rules(bucket, b2_api): 16 | if 'writeBucketNotifications' not in b2_api.account_info.get_allowed()['capabilities']: 17 | pytest.skip('Test account does not have writeBucketNotifications capability') 18 | 19 | assert bucket.set_notification_rules([]) == [] 20 | assert bucket.get_notification_rules() == [] 21 | 22 | notification_rule = { 23 | 'eventTypes': ['b2:ObjectCreated:*'], 24 | 'isEnabled': True, 25 | 'name': 'test-rule', 26 | 'objectNamePrefix': '', 27 | 'targetConfiguration': { 28 | 'customHeaders': [], 29 | 'targetType': 'webhook', 30 | 'url': 'https://example.com/webhook', 31 | 'hmacSha256SigningSecret': 'stringOf32AlphaNumericCharacters', 32 | }, 33 | } 34 | 35 | set_notification_rules = bucket.set_notification_rules([notification_rule]) 36 | assert set_notification_rules == bucket.get_notification_rules() 37 | assert_dict_equal_ignore_extra( 38 | set_notification_rules, 39 | [{**notification_rule, 'isSuspended': False, 'suspensionReason': ''}], 40 | ) 41 | assert bucket.set_notification_rules([]) == [] 42 | 43 | 44 | def test_bucket_update__lifecycle_rules(bucket, b2_api): 45 | lifecycle_rule = { 46 | 'daysFromHidingToDeleting': 1, 47 | 'daysFromUploadingToHiding': 1, 48 | 'daysFromStartingToCancelingUnfinishedLargeFiles': 1, 49 | 'fileNamePrefix': '', 50 | } 51 | 52 | old_rules_list = bucket.lifecycle_rules 53 | 54 | updated_bucket = bucket.update(lifecycle_rules=[lifecycle_rule]) 55 | assert updated_bucket.lifecycle_rules == [lifecycle_rule] 56 | assert bucket.lifecycle_rules is old_rules_list 57 | 58 | updated_bucket = bucket.update(lifecycle_rules=[]) 59 | assert updated_bucket.lifecycle_rules == [] 60 | -------------------------------------------------------------------------------- /test/unit/v0/test_progress.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v0/test_progress.py 4 | # 5 | # Copyright 2019, Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from io import BytesIO 13 | 14 | from ..test_base import TestBase 15 | from .deps import StreamWithHash, hex_sha1_of_bytes 16 | 17 | 18 | class TestHashingStream(TestBase): 19 | def setUp(self): 20 | self.data = b'01234567' 21 | self.stream = StreamWithHash(BytesIO(self.data)) 22 | self.hash = hex_sha1_of_bytes(self.data) 23 | self.expected = self.data + self.hash.encode() 24 | 25 | def test_no_argument(self): 26 | output = self.stream.read() 27 | self.assertEqual(self.expected, output) 28 | 29 | def test_no_argument_less(self): 30 | output = self.stream.read(len(self.data) - 1) 31 | self.assertEqual(len(output), len(self.data) - 1) 32 | output += self.stream.read() 33 | self.assertEqual(self.expected, output) 34 | 35 | def test_no_argument_equal(self): 36 | output = self.stream.read(len(self.data)) 37 | self.assertEqual(len(output), len(self.data)) 38 | output += self.stream.read() 39 | self.assertEqual(self.expected, output) 40 | 41 | def test_no_argument_more(self): 42 | output = self.stream.read(len(self.data) + 1) 43 | self.assertEqual(len(output), len(self.data) + 1) 44 | output += self.stream.read() 45 | self.assertEqual(self.expected, output) 46 | 47 | def test_one_by_one(self): 48 | for expected_byte in self.expected: 49 | self.assertEqual(bytes((expected_byte,)), self.stream.read(1)) 50 | self.assertEqual(b'', self.stream.read(1)) 51 | 52 | def test_large_read(self): 53 | output = self.stream.read(1024) 54 | self.assertEqual(self.expected, output) 55 | self.assertEqual(b'', self.stream.read(1)) 56 | 57 | def test_seek_zero(self): 58 | output0 = self.stream.read() 59 | self.stream.seek(0) 60 | output1 = self.stream.read() 61 | self.assertEqual(output0, output1) 62 | -------------------------------------------------------------------------------- /test/unit/v1/test_progress.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v1/test_progress.py 4 | # 5 | # Copyright 2019, Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from io import BytesIO 13 | 14 | from ..test_base import TestBase 15 | from .deps import StreamWithHash, hex_sha1_of_bytes 16 | 17 | 18 | class TestHashingStream(TestBase): 19 | def setUp(self): 20 | self.data = b'01234567' 21 | self.stream = StreamWithHash(BytesIO(self.data)) 22 | self.hash = hex_sha1_of_bytes(self.data) 23 | self.expected = self.data + self.hash.encode() 24 | 25 | def test_no_argument(self): 26 | output = self.stream.read() 27 | self.assertEqual(self.expected, output) 28 | 29 | def test_no_argument_less(self): 30 | output = self.stream.read(len(self.data) - 1) 31 | self.assertEqual(len(output), len(self.data) - 1) 32 | output += self.stream.read() 33 | self.assertEqual(self.expected, output) 34 | 35 | def test_no_argument_equal(self): 36 | output = self.stream.read(len(self.data)) 37 | self.assertEqual(len(output), len(self.data)) 38 | output += self.stream.read() 39 | self.assertEqual(self.expected, output) 40 | 41 | def test_no_argument_more(self): 42 | output = self.stream.read(len(self.data) + 1) 43 | self.assertEqual(len(output), len(self.data) + 1) 44 | output += self.stream.read() 45 | self.assertEqual(self.expected, output) 46 | 47 | def test_one_by_one(self): 48 | for expected_byte in self.expected: 49 | self.assertEqual(bytes((expected_byte,)), self.stream.read(1)) 50 | self.assertEqual(b'', self.stream.read(1)) 51 | 52 | def test_large_read(self): 53 | output = self.stream.read(1024) 54 | self.assertEqual(self.expected, output) 55 | self.assertEqual(b'', self.stream.read(1)) 56 | 57 | def test_seek_zero(self): 58 | output0 = self.stream.read() 59 | self.stream.seek(0) 60 | output1 = self.stream.read() 61 | self.assertEqual(output0, output1) 62 | -------------------------------------------------------------------------------- /doc/source/glossary.rst: -------------------------------------------------------------------------------- 1 | ######## 2 | Glossary 3 | ######## 4 | 5 | .. glossary:: 6 | 7 | absoluteMinimumPartSize 8 | The smallest large file part size, as indicated during authorization process by the server (in 2019 it used to be ``5MB``, but the server can set it dynamically) 9 | 10 | account ID 11 | An identifier of the B2 account (not login). Looks like this: ``4ba5845d7aaf``. 12 | 13 | application key ID 14 | Since every :term:`account ID` can have multiple access keys associated with it, the keys need to be distinguished from each other. :term:`application key ID` is an identifier of the access key. There are two types of keys: :term:`master application key` and :term:`non-master application key`. 15 | 16 | application key 17 | The secret associated with an :term:`application key ID`, used to authenticate with the server. Looks like this: ``N2Zug0evLcHDlh_L0Z0AJhiGGdY`` or ``0a1bce5ea463a7e4b090ef5bd6bd82b851928ab2c6`` or ``K0014pbwo1zxcIVMnqSNTfWHReU/O3s`` 18 | 19 | b2sdk version 20 | Looks like this: ``v1.0.0`` or ``1.0.0`` and makes version numbers meaningful. See :ref:`Pinning versions ` for more details. 21 | 22 | b2sdk interface version 23 | Looks like this: ``v3`` or ``b2sdk.v3`` and makes maintaining backward compatibility much easier. See :ref:`interface versions ` for more details. 24 | 25 | master application key 26 | This is the first key you have access to, it is available on the B2 web application. This key has all capabilities, access to all :term:`buckets`, and has no file prefix restrictions or expiration. The :term:`application key ID` of the master application key is equal to :term:`account ID`. 27 | 28 | non-master application key 29 | A key which can have restricted capabilities, can only have access to a certain :term:`bucket` or even to just part of it. See ``_ to learn more. Looks like this: ``0014aa9865d6f0000000000b0`` 30 | 31 | bucket 32 | A container that holds files. You can think of buckets as the top-level folders in your B2 Cloud Storage account. There is no limit to the number of files in a bucket, but there is a limit of 100 buckets per account. See ``_ to learn more. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # B2 Python SDK 2 |  [![Continuous Integration](https://github.com/Backblaze/b2-sdk-python/workflows/Continuous%20Integration/badge.svg)](https://github.com/Backblaze/b2-sdk-python/actions?query=workflow%3A%22Continuous+Integration%22) [![License](https://img.shields.io/pypi/l/b2sdk.svg?label=License)](https://pypi.python.org/pypi/b2) [![python versions](https://img.shields.io/pypi/pyversions/b2sdk.svg?label=python%20versions)](https://pypi.python.org/pypi/b2sdk) [![PyPI version](https://img.shields.io/pypi/v/b2sdk.svg?label=PyPI%20version)](https://pypi.python.org/pypi/b2sdk) [![Docs](https://readthedocs.org/projects/b2-sdk-python/badge/?version=master)](https://b2-sdk-python.readthedocs.io/en/master/) 3 | 4 | 5 | 6 | This repository contains a client library and a few handy utilities for easy access to all of the capabilities of B2 Cloud Storage. 7 | 8 | [B2 command-line tool](https://github.com/Backblaze/B2_Command_Line_Tool) is an example of how it can be used to provide command-line access to the B2 service, but there are many possible applications (including FUSE filesystems, storage backend drivers for backup applications etc). 9 | 10 | # Documentation 11 | 12 | The latest documentation is available on [Read the Docs](https://b2-sdk-python.readthedocs.io). 13 | 14 | # Installation 15 | 16 | The sdk can be installed with: 17 | 18 | pip install b2sdk 19 | 20 | # Version policy 21 | 22 | b2sdk follows [Semantic Versioning](https://semver.org/) policy, so in essence the version number is MAJOR.MINOR.PATCH (for example 1.2.3) and: 23 | - we increase MAJOR version when we make incompatible API changes 24 | - we increase MINOR version when we add functionality in a backwards-compatible manner, and 25 | - we increase PATCH version when we make backwards-compatible bug fixes (unless someone relies on the undocumented behavior of a fixed bug) 26 | 27 | Therefore when setting up b2sdk as a dependency, please make sure to match the version appropriately, for example you could put this in your `requirements.txt` to make sure your code is compatible with the `b2sdk` version your user will get from pypi: 28 | 29 | ``` 30 | b2sdk>=2,<3 31 | ``` 32 | 33 | # Release History 34 | 35 | Please refer to the [changelog](CHANGELOG.md). 36 | 37 | # Developer Info 38 | 39 | Please see our [contributing guidelines](CONTRIBUTING.md). 40 | -------------------------------------------------------------------------------- /test/integration/test_file_version_attributes.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/integration/test_file_version_attributes.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import datetime as dt 13 | 14 | from b2sdk.v3.testing import IntegrationTestBase 15 | 16 | 17 | class TestFileVersionAttributes(IntegrationTestBase): 18 | def _assert_object_has_attributes(self, object, kwargs): 19 | for key, value in kwargs.items(): 20 | assert getattr(object, key) == value 21 | 22 | def test_file_info_b2_attributes(self): 23 | # This test checks that attributes that are internally represented as file_info items with prefix `b2-` 24 | # are saved and retrieved correctly. 25 | 26 | bucket = self.create_bucket() 27 | expected_attributes = { 28 | 'cache_control': 'max-age=3600', 29 | 'expires': 'Wed, 21 Oct 2105 07:28:00 GMT', 30 | 'content_disposition': 'attachment; filename="fname.ext"', 31 | 'content_encoding': 'utf-8', 32 | 'content_language': 'en', 33 | } 34 | kwargs = { 35 | **expected_attributes, 36 | 'expires': dt.datetime(2105, 10, 21, 7, 28, tzinfo=dt.timezone.utc), 37 | } 38 | 39 | file_version = bucket.upload_bytes(b'0', 'file', **kwargs) 40 | self._assert_object_has_attributes(file_version, expected_attributes) 41 | 42 | file_version = bucket.get_file_info_by_id(file_version.id_) 43 | self._assert_object_has_attributes(file_version, expected_attributes) 44 | 45 | download_file = bucket.download_file_by_id(file_version.id_) 46 | self._assert_object_has_attributes(download_file.download_version, expected_attributes) 47 | 48 | copied_version = bucket.copy( 49 | file_version.id_, 50 | 'file_copy', 51 | content_type='text/plain', 52 | **{**kwargs, 'content_language': 'de'}, 53 | ) 54 | self._assert_object_has_attributes( 55 | copied_version, {**expected_attributes, 'content_language': 'de'} 56 | ) 57 | -------------------------------------------------------------------------------- /test/unit/v0/test_bounded_queue_executor.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v0/test_bounded_queue_executor.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import concurrent.futures as futures 13 | import time 14 | 15 | from ..test_base import TestBase 16 | from .deps import BoundedQueueExecutor 17 | 18 | 19 | class TestBoundedQueueExecutor(TestBase): 20 | def setUp(self): 21 | unbounded_executor = futures.ThreadPoolExecutor(max_workers=1) 22 | self.executor = BoundedQueueExecutor(unbounded_executor, 1) 23 | 24 | def tearDown(self): 25 | self.executor.shutdown() 26 | 27 | def test_return_future(self): 28 | future_1 = self.executor.submit(lambda: 1) 29 | print(future_1) 30 | self.assertEqual(1, future_1.result()) 31 | 32 | def test_blocking(self): 33 | # This doesn't actually test that it waits, but it does exercise the code. 34 | 35 | # Make some futures using a function that takes a little time. 36 | def sleep_and_return_fcn(n): 37 | def fcn(): 38 | time.sleep(0.01) 39 | return n 40 | 41 | return fcn 42 | 43 | futures = [self.executor.submit(sleep_and_return_fcn(i)) for i in range(10)] 44 | 45 | # Check the answers 46 | answers = list(map(lambda f: f.result(), futures)) 47 | self.assertEqual(list(range(10)), answers) 48 | 49 | def test_no_exceptions(self): 50 | f = self.executor.submit(lambda: 1) 51 | self.executor.shutdown() 52 | self.assertEqual(0, self.executor.get_num_exceptions()) 53 | self.assertTrue(f.exception() is None) 54 | 55 | def test_two_exceptions(self): 56 | def thrower(): 57 | raise Exception('test_exception') 58 | 59 | f1 = self.executor.submit(thrower) 60 | f2 = self.executor.submit(thrower) 61 | self.executor.shutdown() 62 | self.assertEqual(2, self.executor.get_num_exceptions()) 63 | self.assertFalse(f1.exception() is None) 64 | self.assertEqual('test_exception', str(f2.exception())) 65 | -------------------------------------------------------------------------------- /test/unit/v1/test_bounded_queue_executor.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v1/test_bounded_queue_executor.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import concurrent.futures as futures 13 | import time 14 | 15 | from ..test_base import TestBase 16 | from .deps import BoundedQueueExecutor 17 | 18 | 19 | class TestBoundedQueueExecutor(TestBase): 20 | def setUp(self): 21 | unbounded_executor = futures.ThreadPoolExecutor(max_workers=1) 22 | self.executor = BoundedQueueExecutor(unbounded_executor, 1) 23 | 24 | def tearDown(self): 25 | self.executor.shutdown() 26 | 27 | def test_return_future(self): 28 | future_1 = self.executor.submit(lambda: 1) 29 | print(future_1) 30 | self.assertEqual(1, future_1.result()) 31 | 32 | def test_blocking(self): 33 | # This doesn't actually test that it waits, but it does exercise the code. 34 | 35 | # Make some futures using a function that takes a little time. 36 | def sleep_and_return_fcn(n): 37 | def fcn(): 38 | time.sleep(0.01) 39 | return n 40 | 41 | return fcn 42 | 43 | futures = [self.executor.submit(sleep_and_return_fcn(i)) for i in range(10)] 44 | 45 | # Check the answers 46 | answers = list(map(lambda f: f.result(), futures)) 47 | self.assertEqual(list(range(10)), answers) 48 | 49 | def test_no_exceptions(self): 50 | f = self.executor.submit(lambda: 1) 51 | self.executor.shutdown() 52 | self.assertEqual(0, self.executor.get_num_exceptions()) 53 | self.assertTrue(f.exception() is None) 54 | 55 | def test_two_exceptions(self): 56 | def thrower(): 57 | raise Exception('test_exception') 58 | 59 | f1 = self.executor.submit(thrower) 60 | f2 = self.executor.submit(thrower) 61 | self.executor.shutdown() 62 | self.assertEqual(2, self.executor.get_num_exceptions()) 63 | self.assertFalse(f1.exception() is None) 64 | self.assertEqual('test_exception', str(f2.exception())) 65 | -------------------------------------------------------------------------------- /b2sdk/_internal/testing/helpers/buckets.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/testing/helpers/buckets.py 4 | # 5 | # Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import logging 13 | import os 14 | import platform 15 | import random 16 | import secrets 17 | import time 18 | from hashlib import sha256 19 | 20 | from b2sdk._internal.http_constants import BUCKET_NAME_CHARS_UNIQ, BUCKET_NAME_LENGTH_RANGE 21 | 22 | logger = logging.getLogger(__name__) 23 | 24 | GENERAL_BUCKET_NAME_PREFIX = 'sdktst' 25 | BUCKET_NAME_LENGTH = BUCKET_NAME_LENGTH_RANGE[1] 26 | BUCKET_CREATED_AT_MILLIS = 'created_at_millis' 27 | NODE_DESCRIPTION = f'{platform.node()}: {platform.platform()} {platform.python_version()}' 28 | 29 | 30 | def get_seed() -> str: 31 | """ 32 | Get seed for random number generator. 33 | 34 | The `WORKFLOW_ID` variable has to be set in the CI to uniquely identify 35 | the current workflow (including the attempt) 36 | """ 37 | seed = ''.join( 38 | ( 39 | os.getenv('WORKFLOW_ID', secrets.token_hex(8)), 40 | NODE_DESCRIPTION, 41 | str(time.time_ns()), 42 | os.getenv('PYTEST_XDIST_WORKER', 'master'), 43 | ) 44 | ) 45 | return sha256(seed.encode()).hexdigest()[:16] 46 | 47 | 48 | RNG_SEED = get_seed() 49 | RNG = random.Random(RNG_SEED) 50 | RNG_COUNTER = 0 51 | 52 | 53 | def random_token(length: int, chars: str = BUCKET_NAME_CHARS_UNIQ) -> str: 54 | return ''.join(RNG.choice(chars) for _ in range(length)) 55 | 56 | 57 | def bucket_name_part(length: int) -> str: 58 | assert length >= 1 59 | global RNG_COUNTER 60 | RNG_COUNTER += 1 61 | name_part = random_token(length, BUCKET_NAME_CHARS_UNIQ) 62 | logger.info('RNG_SEED: %s', RNG_SEED) 63 | logger.info('RNG_COUNTER: %i, length: %i', RNG_COUNTER, length) 64 | logger.info('name_part: %s', name_part) 65 | logger.info('WORKFLOW_ID: %s', os.getenv('WORKFLOW_ID')) 66 | return name_part 67 | 68 | 69 | def get_bucket_name_prefix(rnd_len: int = 8, prefix: str = GENERAL_BUCKET_NAME_PREFIX) -> str: 70 | return prefix + bucket_name_part(rnd_len) 71 | -------------------------------------------------------------------------------- /b2sdk/v0/account_info.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v0/account_info.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from b2sdk._internal import version_utils 13 | from b2sdk import v1 14 | 15 | 16 | class OldAccountInfoMethods: 17 | """this class contains proxy methods for deprecated signatures renamed for consistency in mid-2019""" 18 | 19 | def get_account_id_or_app_key_id(self): 20 | """ 21 | Return the application key ID used to authenticate. 22 | 23 | :rtype: str 24 | 25 | .. deprecated:: 0.1.6 26 | Use :func:`get_application_key_id` instead. 27 | """ 28 | return self.get_application_key_id() 29 | 30 | @version_utils.rename_argument( 31 | 'account_id_or_app_key_id', 32 | 'application_key_id', 33 | '0.1.5', 34 | None, 35 | ) 36 | def set_auth_data( 37 | self, 38 | account_id, 39 | auth_token, 40 | api_url, 41 | download_url, 42 | minimum_part_size, 43 | application_key, 44 | realm, 45 | allowed=None, 46 | application_key_id=None, 47 | s3_api_url=None, 48 | ): 49 | # we need to enumerate all the parameters and cannot just "*args, **kwargs" because 50 | # the deprecation decorator doesn't feel safe with the kwargs approach 51 | return super().set_auth_data( 52 | account_id, 53 | auth_token, 54 | api_url, 55 | download_url, 56 | minimum_part_size, 57 | application_key, 58 | realm, 59 | allowed, 60 | application_key_id, 61 | s3_api_url=s3_api_url, 62 | ) 63 | 64 | 65 | class AbstractAccountInfo(OldAccountInfoMethods, v1.AbstractAccountInfo): 66 | pass 67 | 68 | 69 | class InMemoryAccountInfo(OldAccountInfoMethods, v1.InMemoryAccountInfo): 70 | pass 71 | 72 | 73 | class UrlPoolAccountInfo(OldAccountInfoMethods, v1.UrlPoolAccountInfo): 74 | pass 75 | 76 | 77 | class SqliteAccountInfo(OldAccountInfoMethods, v1.SqliteAccountInfo): 78 | pass 79 | -------------------------------------------------------------------------------- /test/unit/sync/fixtures.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/sync/fixtures.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import apiver_deps 13 | import pytest 14 | from apiver_deps import ( 15 | DEFAULT_SCAN_MANAGER, 16 | POLICY_MANAGER, 17 | CompareVersionMode, 18 | KeepOrDeleteMode, 19 | NewerFileSyncMode, 20 | Synchronizer, 21 | UploadMode, 22 | ) 23 | 24 | 25 | @pytest.fixture(scope='session') 26 | def synchronizer_factory(): 27 | def get_synchronizer( 28 | policies_manager=DEFAULT_SCAN_MANAGER, 29 | dry_run=False, 30 | allow_empty_source=False, 31 | newer_file_mode=NewerFileSyncMode.RAISE_ERROR, 32 | keep_days_or_delete=KeepOrDeleteMode.NO_DELETE, 33 | keep_days=None, 34 | compare_version_mode=CompareVersionMode.MODTIME, 35 | compare_threshold=None, 36 | sync_policy_manager=POLICY_MANAGER, 37 | upload_mode=UploadMode.FULL, 38 | absolute_minimum_part_size=None, 39 | ): 40 | kwargs = {} 41 | if apiver_deps.V < 2: 42 | assert upload_mode == UploadMode.FULL, 'upload_mode not supported in apiver < 2' 43 | assert ( 44 | absolute_minimum_part_size is None 45 | ), 'absolute_minimum_part_size not supported in apiver < 2' 46 | else: 47 | kwargs = dict( 48 | upload_mode=upload_mode, 49 | absolute_minimum_part_size=absolute_minimum_part_size, 50 | ) 51 | 52 | return Synchronizer( 53 | 1, 54 | policies_manager=policies_manager, 55 | dry_run=dry_run, 56 | allow_empty_source=allow_empty_source, 57 | newer_file_mode=newer_file_mode, 58 | keep_days_or_delete=keep_days_or_delete, 59 | keep_days=keep_days, 60 | compare_version_mode=compare_version_mode, 61 | compare_threshold=compare_threshold, 62 | sync_policy_manager=sync_policy_manager, 63 | **kwargs, 64 | ) 65 | 66 | return get_synchronizer 67 | 68 | 69 | @pytest.fixture 70 | def synchronizer(synchronizer_factory): 71 | return synchronizer_factory() 72 | -------------------------------------------------------------------------------- /b2sdk/_internal/transfer/outbound/large_file_upload_state.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/_internal/transfer/outbound/large_file_upload_state.py 4 | # 5 | # Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import threading 13 | 14 | 15 | class LargeFileUploadState: 16 | """ 17 | Track the status of uploading a large file, accepting updates 18 | from the tasks that upload each of the parts. 19 | 20 | The aggregated progress is passed on to a ProgressListener that 21 | reports the progress for the file as a whole. 22 | 23 | This class is THREAD SAFE. 24 | """ 25 | 26 | def __init__(self, file_progress_listener): 27 | """ 28 | :param b2sdk.v2.AbstractProgressListener file_progress_listener: a progress listener object to use. Use :py:class:`b2sdk.v2.DoNothingProgressListener` to disable. 29 | """ 30 | self.lock = threading.RLock() 31 | self.error_message = None 32 | self.file_progress_listener = file_progress_listener 33 | self.part_number_to_part_state = {} 34 | self.bytes_completed = 0 35 | 36 | def set_error(self, message): 37 | """ 38 | Set an error message. 39 | 40 | :param str message: an error message 41 | """ 42 | with self.lock: 43 | self.error_message = message 44 | 45 | def has_error(self): 46 | """ 47 | Check whether an error occurred. 48 | 49 | :rtype: bool 50 | """ 51 | with self.lock: 52 | return self.error_message is not None 53 | 54 | def get_error_message(self): 55 | """ 56 | Fetche an error message. 57 | 58 | :return: an error message 59 | :rtype: str 60 | """ 61 | with self.lock: 62 | assert self.has_error() 63 | return self.error_message 64 | 65 | def update_part_bytes(self, bytes_delta): 66 | """ 67 | Update listener progress info. 68 | 69 | :param int bytes_delta: number of bytes to increase a progress for 70 | """ 71 | with self.lock: 72 | self.bytes_completed += bytes_delta 73 | self.file_progress_listener.bytes_completed(self.bytes_completed) 74 | -------------------------------------------------------------------------------- /test/unit/v2/test_raw_api.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v2/test_raw_api.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from unittest.mock import Mock 13 | 14 | import pytest 15 | 16 | from b2sdk import v3 17 | from b2sdk.v2 import B2Http, B2RawHTTPApi 18 | from test.helpers import patch_bind_params 19 | 20 | 21 | @pytest.fixture 22 | def dummy_b2_raw_http_api(): 23 | return B2RawHTTPApi(Mock(spec=B2Http)) 24 | 25 | 26 | def test_b2_raw_http_api__get_upload_file_headers__supports_file_infos( 27 | dummy_b2_raw_http_api, file_info 28 | ): 29 | """Test v2.B2RawHTTPApi.get_upload_file_headers support of deprecated file_infos param""" 30 | with patch_bind_params(v3.B2RawHTTPApi, 'get_upload_file_headers') as mock_method, pytest.warns( 31 | DeprecationWarning, match=r'deprecated argument' 32 | ): 33 | dummy_b2_raw_http_api.get_upload_file_headers( 34 | 'upload_auth_token', 35 | 'file_name', 36 | 123, # content_length 37 | 'content_type', 38 | 'content_sha1', 39 | file_infos=file_info, 40 | server_side_encryption=None, 41 | file_retention=None, 42 | legal_hold=None, 43 | custom_upload_timestamp=None, 44 | ) 45 | assert mock_method.get_bound_call_args()['file_info'] == file_info 46 | assert 'file_infos' not in mock_method.call_args[1] 47 | 48 | 49 | def test_b2_raw_http_api__upload_file__supports_file_infos(dummy_b2_raw_http_api, file_info): 50 | """Test v2.B2RawHTTPApi.upload_file support of deprecated file_infos param""" 51 | with patch_bind_params(v3.B2RawHTTPApi, 'upload_file') as mock_method, pytest.warns( 52 | DeprecationWarning, match=r'deprecated argument' 53 | ): 54 | dummy_b2_raw_http_api.upload_file( 55 | 'upload_id', 56 | 'upload_auth_token', 57 | 'file_name', 58 | 123, # content_length 59 | 'content_type', 60 | 'content_sha1', 61 | file_infos=file_info, 62 | data_stream='data_stream', 63 | ) 64 | assert mock_method.get_bound_call_args()['file_info'] == file_info 65 | assert 'file_infos' not in mock_method.call_args[1] 66 | -------------------------------------------------------------------------------- /test/unit/utils/test_range_.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/utils/test_range_.py 4 | # 5 | # Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | import pytest 11 | 12 | 13 | def test_range_initialization(apiver_module): 14 | r = apiver_module.Range(0, 10) 15 | assert r.start == 0 16 | assert r.end == 10 17 | 18 | 19 | def test_range_eq(apiver_module): 20 | r = apiver_module.Range(5, 10) 21 | assert r == apiver_module.Range(5, 10) 22 | assert r != apiver_module.Range(5, 11) 23 | assert r != apiver_module.Range(6, 10) 24 | 25 | 26 | def test_range_initialization_invalid(apiver_module): 27 | with pytest.raises(AssertionError): 28 | apiver_module.Range(10, 0) 29 | 30 | 31 | def test_range_from_header(apiver_module): 32 | r = apiver_module.Range.from_header('bytes=0-11') 33 | assert r.start == 0 34 | assert r.end == 11 35 | 36 | 37 | @pytest.mark.parametrize( 38 | 'raw_range_header, start, end, total_length', 39 | [ 40 | ('bytes 0-11', 0, 11, None), 41 | ('bytes 1-11/*', 1, 11, None), 42 | ('bytes 10-110/200', 10, 110, 200), 43 | ], 44 | ) 45 | def test_range_from_header_with_size(apiver_module, raw_range_header, start, end, total_length): 46 | r, length = apiver_module.Range.from_header_with_size(raw_range_header) 47 | assert r.start == start 48 | assert r.end == end 49 | assert length == total_length 50 | 51 | 52 | def test_range_size(apiver_module): 53 | r = apiver_module.Range(0, 10) 54 | assert r.size() == 11 55 | 56 | 57 | def test_range_subrange(apiver_module): 58 | r = apiver_module.Range(1, 10) 59 | assert r.subrange(0, 9) == apiver_module.Range(1, 10) 60 | assert r.subrange(2, 5) == apiver_module.Range(3, 6) 61 | 62 | 63 | def test_range_subrange_invalid(apiver_module): 64 | r = apiver_module.Range(0, 10) 65 | with pytest.raises(AssertionError): 66 | r.subrange(5, 15) 67 | 68 | 69 | def test_range_as_tuple(apiver_module): 70 | r = apiver_module.Range(0, 10) 71 | assert r.as_tuple() == (0, 10) 72 | 73 | 74 | def test_range_repr(apiver_module): 75 | r = apiver_module.Range(0, 10) 76 | assert repr(r) == 'Range(0, 10)' 77 | 78 | 79 | def test_empty_range(apiver_module): 80 | r = apiver_module.EMPTY_RANGE 81 | assert r.size() == 0 82 | -------------------------------------------------------------------------------- /b2sdk/v1/file_metadata.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: b2sdk/v1/file_metadata.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | import b2sdk.v2 as v2 13 | 14 | 15 | class FileMetadata: 16 | """ 17 | Hold information about a file which is being downloaded. 18 | """ 19 | 20 | UNVERIFIED_CHECKSUM_PREFIX = 'unverified:' 21 | 22 | def __init__( 23 | self, 24 | file_id, 25 | file_name, 26 | content_type, 27 | content_length, 28 | content_sha1, 29 | file_info, 30 | ): 31 | self.file_id = file_id 32 | self.file_name = file_name 33 | self.content_type = content_type 34 | self.content_length = content_length 35 | self.content_sha1, self.content_sha1_verified = self._decode_content_sha1(content_sha1) 36 | self.file_info = file_info 37 | 38 | def as_info_dict(self): 39 | return { 40 | 'fileId': self.file_id, 41 | 'fileName': self.file_name, 42 | 'contentType': self.content_type, 43 | 'contentLength': self.content_length, 44 | 'contentSha1': self._encode_content_sha1(self.content_sha1, self.content_sha1_verified), 45 | 'fileInfo': self.file_info, 46 | } 47 | 48 | @classmethod 49 | def _decode_content_sha1(cls, content_sha1): 50 | if content_sha1.startswith(cls.UNVERIFIED_CHECKSUM_PREFIX): 51 | return content_sha1[len(cls.UNVERIFIED_CHECKSUM_PREFIX) :], False 52 | return content_sha1, True 53 | 54 | @classmethod 55 | def _encode_content_sha1(cls, content_sha1, content_sha1_verified): 56 | if not content_sha1_verified: 57 | return f'{cls.UNVERIFIED_CHECKSUM_PREFIX}{content_sha1}' 58 | return content_sha1 59 | 60 | @classmethod 61 | def from_download_version(cls, download_version: v2.DownloadVersion): 62 | return cls( 63 | file_id=download_version.id_, 64 | file_name=download_version.file_name, 65 | content_type=download_version.content_type, 66 | content_length=download_version.content_length, 67 | content_sha1=download_version.content_sha1, 68 | file_info=download_version.file_info, 69 | ) 70 | -------------------------------------------------------------------------------- /test/unit/v0/test_scan_policies.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v0/test_scan_policies.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from ..test_base import TestBase 13 | from .deps import DEFAULT_SCAN_MANAGER, ScanPoliciesManager 14 | from .deps_exception import InvalidArgument 15 | 16 | 17 | class TestScanPolicies(TestBase): 18 | def test_default(self): 19 | self.assertFalse(DEFAULT_SCAN_MANAGER.should_exclude_directory('')) 20 | self.assertFalse(DEFAULT_SCAN_MANAGER.should_exclude_file('')) 21 | self.assertFalse(DEFAULT_SCAN_MANAGER.should_exclude_directory('a')) 22 | self.assertFalse(DEFAULT_SCAN_MANAGER.should_exclude_file('a')) 23 | 24 | def test_exclude_include(self): 25 | policy = ScanPoliciesManager(exclude_file_regexes=('a', 'b'), include_file_regexes=('ab',)) 26 | self.assertTrue(policy.should_exclude_file('alfa')) 27 | self.assertTrue(policy.should_exclude_file('bravo')) 28 | self.assertFalse(policy.should_exclude_file('abend')) 29 | self.assertFalse(policy.should_exclude_file('charlie')) 30 | 31 | def test_exclude_dir(self): 32 | policy = ScanPoliciesManager(exclude_dir_regexes=('alfa', 'bravo$')) 33 | self.assertTrue(policy.should_exclude_directory('alfa')) 34 | self.assertTrue(policy.should_exclude_directory('alfa2')) 35 | self.assertTrue(policy.should_exclude_directory('alfa/hello')) 36 | 37 | self.assertTrue(policy.should_exclude_directory('bravo')) 38 | self.assertFalse(policy.should_exclude_directory('bravo2')) 39 | self.assertFalse(policy.should_exclude_directory('bravo/hello')) 40 | 41 | self.assertTrue(policy.should_exclude_file('alfa/foo')) 42 | self.assertTrue(policy.should_exclude_file('alfa2/hello/foo')) 43 | self.assertTrue(policy.should_exclude_file('alfa/hello/foo.txt')) 44 | 45 | self.assertTrue(policy.should_exclude_file('bravo/foo')) 46 | self.assertFalse(policy.should_exclude_file('bravo2/hello/foo')) 47 | self.assertTrue(policy.should_exclude_file('bravo/hello/foo.txt')) 48 | 49 | def test_include_without_exclude(self): 50 | with self.assertRaises(InvalidArgument): 51 | ScanPoliciesManager(include_file_regexes=('.*',)) 52 | -------------------------------------------------------------------------------- /test/unit/v1/test_scan_policies.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/v1/test_scan_policies.py 4 | # 5 | # Copyright 2019 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | from __future__ import annotations 11 | 12 | from ..test_base import TestBase 13 | from .deps import DEFAULT_SCAN_MANAGER, ScanPoliciesManager 14 | from .deps_exception import InvalidArgument 15 | 16 | 17 | class TestScanPolicies(TestBase): 18 | def test_default(self): 19 | self.assertFalse(DEFAULT_SCAN_MANAGER.should_exclude_directory('')) 20 | self.assertFalse(DEFAULT_SCAN_MANAGER.should_exclude_file('')) 21 | self.assertFalse(DEFAULT_SCAN_MANAGER.should_exclude_directory('a')) 22 | self.assertFalse(DEFAULT_SCAN_MANAGER.should_exclude_file('a')) 23 | 24 | def test_exclude_include(self): 25 | policy = ScanPoliciesManager(exclude_file_regexes=('a', 'b'), include_file_regexes=('ab',)) 26 | self.assertTrue(policy.should_exclude_file('alfa')) 27 | self.assertTrue(policy.should_exclude_file('bravo')) 28 | self.assertFalse(policy.should_exclude_file('abend')) 29 | self.assertFalse(policy.should_exclude_file('charlie')) 30 | 31 | def test_exclude_dir(self): 32 | policy = ScanPoliciesManager(exclude_dir_regexes=('alfa', 'bravo$')) 33 | self.assertTrue(policy.should_exclude_directory('alfa')) 34 | self.assertTrue(policy.should_exclude_directory('alfa2')) 35 | self.assertTrue(policy.should_exclude_directory('alfa/hello')) 36 | 37 | self.assertTrue(policy.should_exclude_directory('bravo')) 38 | self.assertFalse(policy.should_exclude_directory('bravo2')) 39 | self.assertFalse(policy.should_exclude_directory('bravo/hello')) 40 | 41 | self.assertTrue(policy.should_exclude_file('alfa/foo')) 42 | self.assertTrue(policy.should_exclude_file('alfa2/hello/foo')) 43 | self.assertTrue(policy.should_exclude_file('alfa/hello/foo.txt')) 44 | 45 | self.assertTrue(policy.should_exclude_file('bravo/foo')) 46 | self.assertFalse(policy.should_exclude_file('bravo2/hello/foo')) 47 | self.assertTrue(policy.should_exclude_file('bravo/hello/foo.txt')) 48 | 49 | def test_include_without_exclude(self): 50 | with self.assertRaises(InvalidArgument): 51 | ScanPoliciesManager(include_file_regexes=('.*',)) 52 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. todolist:: 2 | 3 | .. note:: **Event Notifications** feature is now in **Private Preview**. See https://www.backblaze.com/blog/announcing-event-notifications/ for details. 4 | 5 | ######################################### 6 | Overview 7 | ######################################### 8 | 9 | **b2sdk** is a client library for easy access to all of the capabilities of B2 Cloud Storage. 10 | 11 | `B2 command-line tool `_ is an example of how it can be used 12 | to provide command-line access to the B2 service, but there are many possible applications 13 | (including `FUSE filesystems `_, storage backend drivers for backup applications etc). 14 | 15 | ######################################### 16 | Why use b2sdk? 17 | ######################################### 18 | 19 | .. todo:: 20 | delete doc/source/b2sdk? 21 | 22 | .. todo:: 23 | describe raw_simulator in detail 24 | 25 | .. todo:: 26 | fix list consistency style in "Why use b2sdk?", add links 27 | 28 | When building an application which uses B2 cloud, it is possible to implement an independent B2 API client, but using **b2sdk** allows for: 29 | 30 | - reuse of code that is already written, with hundreds of unit tests 31 | - use of **Synchronizer**, a high-performance, parallel rsync-like utility 32 | - developer-friendly library :ref:`api version policy ` which guards your program against incompatible changes 33 | - `B2 integration checklist `_ is passed automatically 34 | - **raw_simulator** makes it easy to mock the B2 cloud for unit testing purposes 35 | - reporting progress of operations to an object of your choice 36 | - exception hierarchy makes it easy to display informative messages to users 37 | - interrupted transfers are automatically continued 38 | - **b2sdk** has been developed for 3 years before it version 1.0.0 was released. It's stable and mature. 39 | 40 | 41 | ######################################### 42 | Documentation index 43 | ######################################### 44 | 45 | .. toctree:: 46 | 47 | install 48 | tutorial 49 | quick_start 50 | server_side_encryption 51 | advanced 52 | glossary 53 | api_types 54 | api_reference 55 | contributing 56 | 57 | 58 | ######################################### 59 | Indices and tables 60 | ######################################### 61 | 62 | * :ref:`genindex` 63 | * :ref:`modindex` 64 | * :ref:`search` 65 | -------------------------------------------------------------------------------- /test/unit/utils/test_escape.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: test/unit/utils/test_escape.py 4 | # 5 | # Copyright 2023 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | import pytest 11 | 12 | from b2sdk._internal.utils.escape import ( 13 | escape_control_chars, 14 | substitute_control_chars, 15 | unprintable_to_hex, 16 | ) 17 | 18 | 19 | @pytest.mark.parametrize( 20 | ( 21 | 'input_', 22 | 'expected_unprintable_to_hex', 23 | 'expected_escape_control_chars', 24 | 'expected_substitute_control_chars', 25 | ), 26 | [ 27 | ('', '', '', ('', False)), 28 | (' abc-z', ' abc-z', "' abc-z'", (' abc-z', False)), 29 | ('a\x7fb', 'a\\x7fb', "'a\\x7fb'", ('a�b', True)), 30 | ('a\x00b a\x9fb ', 'a\\x00b a\\x9fb ', "'a\\x00b a\\x9fb '", ('a�b a�b ', True)), 31 | ('a\x7fb\nc', 'a\\x7fb\nc', "'a\\x7fb\nc'", ('a�b\nc', True)), 32 | ('\x9bT\x9bEtest', '\\x9bT\\x9bEtest', "'\\x9bT\\x9bEtest'", ('�T�Etest', True)), 33 | ( 34 | '\x1b[32mC\x1b[33mC\x1b[34mI', 35 | '\\x1b[32mC\\x1b[33mC\\x1b[34mI', 36 | "'\\x1b[32mC\\x1b[33mC\\x1b[34mI'", 37 | ('�[32mC�[33mC�[34mI', True), 38 | ), 39 | ], 40 | ) 41 | def test_unprintable_to_hex( 42 | input_, 43 | expected_unprintable_to_hex, 44 | expected_escape_control_chars, 45 | expected_substitute_control_chars, 46 | ): 47 | assert unprintable_to_hex(input_) == expected_unprintable_to_hex 48 | assert escape_control_chars(input_) == expected_escape_control_chars 49 | assert substitute_control_chars(input_) == expected_substitute_control_chars 50 | 51 | 52 | def test_unprintable_to_hex__none(): 53 | """ 54 | Test that unprintable_to_hex handles None. 55 | 56 | This was unintentionally supported and is only kept for compatibility. 57 | """ 58 | assert unprintable_to_hex(None) is None # type: ignore 59 | 60 | 61 | def test_escape_control_chars__none(): 62 | """ 63 | Test that escape_control_chars handles None. 64 | 65 | This was unintentionally supported and is only kept for compatibility. 66 | """ 67 | assert escape_control_chars(None) is None # type: ignore 68 | 69 | 70 | def test_substitute_control_chars__none(): 71 | with pytest.raises(TypeError): 72 | substitute_control_chars(None) # type: ignore 73 | --------------------------------------------------------------------------------