Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor tweaks #186

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
* `B2Api.get_file_info` returns a `FileVersion` object in v2
* `B2RawApi` renamed to `B2RawHTTPApi`
* `B2HTTP` tests are now common
* `B2HttpApiConfig` class introduced to provide parameters like `user_agent_append` to `B2Api` without using internal classes in v2
* `Bucket.update` return s a `Bucket` object in v2
* `Bucket.ls` argument `show_versions` renamed to `latest_only` in v2

## [1.9.0] - 2021-06-07

### Added
Expand Down
4 changes: 3 additions & 1 deletion b2sdk/_v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
# raw_api

from b2sdk.raw_api import AbstractRawApi
from b2sdk.raw_api import B2RawApi
from b2sdk.raw_api import B2RawHTTPApi
from b2sdk.raw_api import MetadataDirectiveMode

# stream
Expand Down Expand Up @@ -198,6 +198,8 @@
# other

from b2sdk.b2http import B2Http
from b2sdk.api_config import B2HttpApiConfig
from b2sdk.api_config import DEFAULT_HTTP_API_CONFIG
from b2sdk.b2http import ClockSkewHook
from b2sdk.b2http import HttpCallback
from b2sdk.b2http import ResponseContextManager
Expand Down
1 change: 0 additions & 1 deletion b2sdk/account_info/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,6 @@ def allowed_is_valid(cls, allowed):
('capabilities' in allowed) and ('namePrefix' in allowed)
)

# TODO: make a decorator for set_auth_data()
@abstractmethod
def _set_auth_data(
self, account_id, auth_token, api_url, download_url, recommended_part_size,
Expand Down
63 changes: 26 additions & 37 deletions b2sdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@
#
######################################################################

from typing import Any, Dict, Optional
from typing import Optional

from .account_info.abstract import AbstractAccountInfo
from .cache import AbstractCache
from .bucket import Bucket, BucketFactory
from .encryption.setting import EncryptionSetting
from .exception import NonExistentBucket, RestrictedBucket
from .file_lock import FileRetentionSetting, LegalHold
from .file_version import FileIdAndName, FileVersionFactory
from .file_version import FileIdAndName, FileVersion, FileVersionFactory
from .large_file.services import LargeFileServices
from .raw_api import API_VERSION
from .api_config import B2HttpApiConfig, DEFAULT_HTTP_API_CONFIG
from .session import B2Session
from .transfer import (
CopyManager,
Expand Down Expand Up @@ -66,7 +69,7 @@ class B2Api(metaclass=B2TraceMeta):
"""
Provide file-level access to B2 services.

While :class:`b2sdk.v1.B2RawApi` provides direct access to the B2 web APIs, this
While :class:`b2sdk.v1.B2RawHTTPApi` provides direct access to the B2 web APIs, this
class handles several things that simplify the task of uploading
and downloading files:

Expand All @@ -89,39 +92,28 @@ class handles several things that simplify the task of uploading

def __init__(
self,
account_info=None,
cache=None,
raw_api=None,
max_upload_workers=10,
max_copy_workers=10
account_info: Optional[AbstractAccountInfo] = None,
cache: Optional[AbstractCache] = None,
max_upload_workers: int = 10,
max_copy_workers: int = 10,
api_config: B2HttpApiConfig = DEFAULT_HTTP_API_CONFIG,
):
"""
Initialize the API using the given account info.

:param account_info: an instance of :class:`~b2sdk.v1.UrlPoolAccountInfo`,
or any custom class derived from
:class:`~b2sdk.v1.AbstractAccountInfo`
To learn more about Account Info objects, see here
:param account_info: To learn more about Account Info objects, see here
:class:`~b2sdk.v1.SqliteAccountInfo`

:param cache: an instance of the one of the following classes:
:class:`~b2sdk.cache.DummyCache`, :class:`~b2sdk.cache.InMemoryCache`,
:class:`~b2sdk.cache.AuthInfoCache`,
or any custom class derived from :class:`~b2sdk.cache.AbstractCache`
It is used by B2Api to cache the mapping between bucket name and bucket ids.
:param cache: It is used by B2Api to cache the mapping between bucket name and bucket ids.
default is :class:`~b2sdk.cache.DummyCache`

:param raw_api: an instance of one of the following classes:
:class:`~b2sdk.raw_api.B2RawApi`, :class:`~b2sdk.raw_simulator.RawSimulator`,
or any custom class derived from :class:`~b2sdk.raw_api.AbstractRawApi`
It makes network-less unit testing simple by using :class:`~b2sdk.raw_simulator.RawSimulator`,
in tests and :class:`~b2sdk.raw_api.B2RawApi` in production.
default is :class:`~b2sdk.raw_api.B2RawApi`

:param int max_upload_workers: a number of upload threads, default is 10
:param int max_copy_workers: a number of copy threads, default is 10
:param max_upload_workers: a number of upload threads
:param max_copy_workers: a number of copy threads
:param api_config:
"""
self.session = self.SESSION_CLASS(account_info=account_info, cache=cache, raw_api=raw_api)
self.session = self.SESSION_CLASS(
account_info=account_info, cache=cache, api_config=api_config
)
self.file_version_factory = self.FILE_VERSION_FACTORY_CLASS(self)
self.services = Services(
self,
Expand All @@ -141,8 +133,8 @@ def cache(self):
def raw_api(self):
"""
.. warning::
:class:`~b2sdk.raw_api.B2RawApi` attribute is deprecated.
:class:`~b2sdk.session.B2Session` expose all :class:`~b2sdk.raw_api.B2RawApi` methods now."""
:class:`~b2sdk.raw_api.B2RawHTTPApi` attribute is deprecated.
:class:`~b2sdk.session.B2Session` expose all :class:`~b2sdk.raw_api.B2RawHTTPApi` methods now."""
return self.session.raw_api

def authorize_automatically(self):
Expand Down Expand Up @@ -490,18 +482,15 @@ def list_keys(self, start_application_key_id=None):
)

# other
def get_file_info(self, file_id: str) -> Dict[str, Any]:
def get_file_info(self, file_id: str) -> FileVersion:
"""
Legacy interface which just returns whatever remote API returns.

.. todo::
get_file_info() should return a File with .delete(), copy(), rename(), read() and so on
Gets info about file version.

:param str file_id: the id of the file who's info will be retrieved.
:return: The parsed response
:rtype: dict
"""
return self.session.get_file_info_by_id(file_id)
return self.file_version_factory.from_api_response(
self.session.get_file_info_by_id(file_id)
)

def check_bucket_name_restrictions(self, bucket_name: str):
"""
Expand Down
37 changes: 37 additions & 0 deletions b2sdk/api_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
######################################################################
#
# File: b2sdk/api_config.py
#
# Copyright 2021 Backblaze Inc. All Rights Reserved.
#
# License https://www.backblaze.com/using_b2_code.html
#
######################################################################

import requests

from typing import Optional

from types import ModuleType


class B2HttpApiConfig:
def __init__(
self,
requests_module: ModuleType = requests,
install_clock_skew_hook: bool = True,
user_agent_append: Optional[str] = None,
):
"""
A structure with params to be passed to low level API.

:param requests_module: a reference to requests module
:param bool install_clock_skew_hook: if True, install a clock skew hook
:param str user_agent_append: if provided, the string will be appended to the User-Agent
"""
self.requests_module = requests_module
self.install_clock_skew_hook = install_clock_skew_hook
self.user_agent_append = user_agent_append


DEFAULT_HTTP_API_CONFIG = B2HttpApiConfig()
14 changes: 5 additions & 9 deletions b2sdk/b2http.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
B2Error, B2RequestTimeoutDuringUpload, BadDateFormat, BrokenPipe, B2ConnectionError,
B2RequestTimeout, ClockSkew, ConnectionReset, interpret_b2_error, UnknownError, UnknownHost
)
from .api_config import B2HttpApiConfig, DEFAULT_HTTP_API_CONFIG
from .version import USER_AGENT

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -250,20 +251,15 @@ class B2Http(object):
# timeout for HTTP GET/POST requests
TIMEOUT = 900 # 15 minutes as server-side copy can take time

def __init__(self, requests_module=None, install_clock_skew_hook=True, user_agent_append=None):
def __init__(self, api_config: B2HttpApiConfig = DEFAULT_HTTP_API_CONFIG):
"""
Initialize with a reference to the requests module, which makes
it easy to mock for testing.

:param requests_module: a reference to requests module
:param bool install_clock_skew_hook: if True, install a clock skew hook
:param str user_agent_append: if provided, the string will be appended to the User-Agent
"""
requests_to_use = requests_module or requests
self.user_agent = self._get_user_agent(user_agent_append)
self.session = requests_to_use.Session()
self.user_agent = self._get_user_agent(api_config.user_agent_append)
self.session = api_config.requests_module.Session()
self.callbacks = []
if install_clock_skew_hook:
if api_config.install_clock_skew_hook:
self.add_callback(ClockSkewHook())

def add_callback(self, callback):
Expand Down
60 changes: 34 additions & 26 deletions b2sdk/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,16 @@ def set_type(self, bucket_type):

def update(
self,
bucket_type=None,
bucket_info=None,
cors_rules=None,
lifecycle_rules=None,
if_revision_is=None,
bucket_type: Optional[str] = None,
bucket_info: Optional[dict] = None,
cors_rules: Optional[dict] = None,
lifecycle_rules: Optional[dict] = None,
if_revision_is: Optional[int] = None,
default_server_side_encryption: Optional[EncryptionSetting] = None,
default_retention: Optional[BucketRetentionSetting] = None,
):
"""
Update various bucket parameters.
For legacy reasons in apiver v1 it returns whatever server returned on b2_update_bucket call, v2 will change that.

:param str bucket_type: a bucket type
:param dict bucket_info: an info to store with a bucket
Expand All @@ -135,16 +134,19 @@ def update(
:param b2sdk.v1.BucketRetentionSetting default_retention: bucket default retention setting
"""
account_id = self.api.account_info.get_account_id()
return self.api.session.update_bucket(
account_id,
self.id_,
bucket_type=bucket_type,
bucket_info=bucket_info,
cors_rules=cors_rules,
lifecycle_rules=lifecycle_rules,
if_revision_is=if_revision_is,
default_server_side_encryption=default_server_side_encryption,
default_retention=default_retention,
return BucketFactory.from_api_bucket_dict(
self.api,
self.api.session.update_bucket(
account_id,
self.id_,
bucket_type=bucket_type,
bucket_info=bucket_info,
cors_rules=cors_rules,
lifecycle_rules=lifecycle_rules,
if_revision_is=if_revision_is,
default_server_side_encryption=default_server_side_encryption,
default_retention=default_retention,
)
)

def cancel_large_file(self, file_id):
Expand Down Expand Up @@ -230,7 +232,7 @@ def get_file_info_by_id(self, file_id: str) -> FileVersion:
:param str file_id: the id of the file who's info will be retrieved.
:rtype: generator[b2sdk.v1.FileVersionInfo]
"""
return self.api.file_version_factory.from_api_response(self.api.get_file_info(file_id))
return self.api.get_file_info(file_id)

def get_file_info_by_name(self, file_name: str) -> FileVersion:
"""
Expand Down Expand Up @@ -299,7 +301,13 @@ def list_file_versions(self, file_name, fetch_count=None):
if start_file_name is None:
return

def ls(self, folder_to_list='', show_versions=False, recursive=False, fetch_count=10000):
def ls(
self,
folder_to_list: str = '',
latest_only: bool = True,
recursive: bool = False,
fetch_count: Optional[int] = 10000
):
"""
Pretend that folders exist and yields the information about the files in a folder.

Expand All @@ -311,12 +319,12 @@ def ls(self, folder_to_list='', show_versions=False, recursive=False, fetch_coun
When the `recursive` flag is set, lists all of the files in the given
folder, and all of its sub-folders.

:param str folder_to_list: the name of the folder to list; must not start with "/".
:param folder_to_list: the name of the folder to list; must not start with "/".
Empty string means top-level folder
:param bool show_versions: when ``True`` returns info about all versions of a file,
when ``False``, just returns info about the most recent versions
:param bool recursive: if ``True``, list folders recursively
:param int,None fetch_count: how many entries to return or ``None`` to use the default. Acceptable values: 1 - 10000
:param latest_only: when ``False`` returns info about all versions of a file,
when ``True``, just returns info about the most recent versions
:param recursive: if ``True``, list folders recursively
:param fetch_count: how many entries to return or ``None`` to use the default. Acceptable values: 1 - 10000
:rtype: generator[tuple[b2sdk.v1.FileVersionInfo, str]]
:returns: generator of (file_version, folder_name) tuples

Expand All @@ -342,12 +350,12 @@ def ls(self, folder_to_list='', show_versions=False, recursive=False, fetch_coun
start_file_id = None
session = self.api.session
while True:
if show_versions:
if latest_only:
response = session.list_file_names(self.id_, start_file_name, fetch_count, prefix)
else:
response = session.list_file_versions(
self.id_, start_file_name, start_file_id, fetch_count, prefix
)
else:
response = session.list_file_names(self.id_, start_file_name, fetch_count, prefix)
for entry in response['files']:
file_version = self.api.file_version_factory.from_api_response(entry)
if not file_version.file_name.startswith(prefix):
Expand Down
22 changes: 22 additions & 0 deletions b2sdk/file_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,17 @@ def as_dict(self):
"retainUntilTimestamp": self.retain_until,
}

def as_dict_with_auth(self):
if self == UNKNOWN_FILE_RETENTION_SETTING:
return {
"isClientAuthorizedToRead": False,
"value": None,
}
return {
"isClientAuthorizedToRead": True,
"value": self.as_dict(),
}

def add_to_to_upload_headers(self, headers):
if self.mode is RetentionMode.UNKNOWN:
raise ValueError('cannot use an unknown file retention setting in requests')
Expand Down Expand Up @@ -247,6 +258,17 @@ def to_dict_repr(self):
return self.__class__.UNKNOWN.value
raise ValueError('Unrepresentable value')

def as_dict_with_auth(self):
if self == self.__class__.UNKNOWN:
return {
"isClientAuthorizedToRead": False,
"value": None,
}
return {
"isClientAuthorizedToRead": True,
"value": self.to_dict_repr(),
}


class BucketRetentionSetting:
"""Represent bucket's default file retention settings, i.e. whether the files should be retained, in which mode
Expand Down
Loading