Skip to content

Commit b343864

Browse files
BucketStructure introduced
1 parent 002dd22 commit b343864

File tree

10 files changed

+295
-26
lines changed

10 files changed

+295
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88

99
### Added
10+
* Add `BucketStructure` to hold info about a bucket
1011
* Add `include_existing_files` parameter to `ReplicationSetupHelper`
1112

1213
### Fixed

b2sdk/_v3/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from b2sdk.api import Services
1818
from b2sdk.bucket import Bucket
1919
from b2sdk.bucket import BucketFactory
20+
from b2sdk.bucket import BucketStructure
21+
from b2sdk.bucket import ValueNotSet
2022
from b2sdk.raw_api import ALL_CAPABILITIES, REALM_URLS
2123

2224
# encryption

b2sdk/bucket.py

Lines changed: 184 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010

1111
import logging
1212

13-
from typing import Optional, Tuple
13+
from typing import Optional, Tuple, Union
14+
15+
if False:
16+
from b2sdk.api import B2Api
1417

1518
from .encryption.setting import EncryptionSetting, EncryptionSettingFactory
1619
from .encryption.types import EncryptionMode
@@ -48,16 +51,30 @@
4851
logger = logging.getLogger(__name__)
4952

5053

51-
class Bucket(metaclass=B2TraceMeta):
52-
"""
53-
Provide access to a bucket in B2: listing files, uploading and downloading.
54-
"""
54+
class ValueNotSet:
55+
pass
5556

56-
DEFAULT_CONTENT_TYPE = AUTO_CONTENT_TYPE
57+
58+
class BucketStructure(metaclass=B2TraceMeta):
59+
"""Structure holding all attributes of a bucket."""
60+
61+
id_: Union[str, ValueNotSet]
62+
account_id: Union[str, ValueNotSet]
63+
name: Union[str, ValueNotSet]
64+
type_: Union[str, ValueNotSet]
65+
bucket_info: Union[dict, ValueNotSet]
66+
cors_rules: Union[dict, ValueNotSet]
67+
lifecycle_rules: Union[dict, ValueNotSet]
68+
revision: Union[int, ValueNotSet]
69+
bucket_dict: Union[dict, ValueNotSet]
70+
options_set: Union[set, ValueNotSet]
71+
default_server_side_encryption: Union[EncryptionSetting, ValueNotSet]
72+
default_retention: Union[BucketRetentionSetting, ValueNotSet]
73+
is_file_lock_enabled: Union[Optional[bool], ValueNotSet]
74+
replication: Union[Optional[ReplicationConfiguration], ValueNotSet]
5775

5876
def __init__(
5977
self,
60-
api,
6178
id_,
6279
name=None,
6380
type_=None,
@@ -73,9 +90,10 @@ def __init__(
7390
default_retention: BucketRetentionSetting = UNKNOWN_BUCKET_RETENTION,
7491
is_file_lock_enabled: Optional[bool] = None,
7592
replication: Optional[ReplicationConfiguration] = None,
93+
*,
94+
account_id,
7695
):
7796
"""
78-
:param b2sdk.v2.B2Api api: an API object
7997
:param str id_: a bucket id
8098
:param str name: a bucket name
8199
:param str type_: a bucket type
@@ -89,9 +107,10 @@ def __init__(
89107
:param b2sdk.v2.BucketRetentionSetting default_retention: default retention setting
90108
:param bool is_file_lock_enabled: whether file locking is enabled or not
91109
:param b2sdk.v2.ReplicationConfiguration replication: replication rules for the bucket
110+
:param str account_id: id of the account owning the bucket
92111
"""
93-
self.api = api
94112
self.id_ = id_
113+
self.account_id = account_id
95114
self.name = name
96115
self.type_ = type_
97116
self.bucket_info = bucket_info or {}
@@ -105,6 +124,45 @@ def __init__(
105124
self.is_file_lock_enabled = is_file_lock_enabled
106125
self.replication = replication
107126

127+
def __repr__(self):
128+
return '%s<%s,%s,%s>' % (type(self).__name__, self.id_, self.name, self.type_)
129+
130+
131+
class Bucket(BucketStructure):
132+
"""
133+
Provide access to a bucket in B2: listing files, uploading and downloading.
134+
"""
135+
136+
api: 'B2Api'
137+
id_: str
138+
account_id: str
139+
name: str
140+
type_: str
141+
bucket_info: dict
142+
cors_rules: dict
143+
lifecycle_rules: dict
144+
revision: int
145+
bucket_dict: dict
146+
options_set: set
147+
default_server_side_encryption: EncryptionSetting
148+
default_retention: BucketRetentionSetting
149+
is_file_lock_enabled: Optional[bool]
150+
replication: Optional[ReplicationConfiguration]
151+
152+
DEFAULT_CONTENT_TYPE = AUTO_CONTENT_TYPE
153+
154+
def __init__(
155+
self,
156+
api,
157+
*args,
158+
**kwargs,
159+
):
160+
"""
161+
:param b2sdk.v2.B2Api api: an API object
162+
"""
163+
self.api = api
164+
super().__init__(*args, account_id=self.api.account_info.get_account_id(), **kwargs)
165+
108166
def get_fresh_state(self) -> 'Bucket':
109167
"""
110168
Fetch all the information about this bucket and return a new bucket object.
@@ -960,9 +1018,6 @@ def as_dict(self):
9601018

9611019
return result
9621020

963-
def __repr__(self):
964-
return 'Bucket<%s,%s,%s>' % (self.id_, self.name, self.type_)
965-
9661021

9671022
class BucketFactory:
9681023
"""
@@ -981,6 +1036,123 @@ def from_api_response(cls, api, response):
9811036
"""
9821037
return [cls.from_api_bucket_dict(api, bucket_dict) for bucket_dict in response['buckets']]
9831038

1039+
@classmethod
1040+
def bucket_structure_from_dict(cls, bucket_dict) -> BucketStructure:
1041+
"""
1042+
Turn a dictionary, like this:
1043+
1044+
.. code-block:: python
1045+
1046+
{
1047+
"bucketType": "allPrivate",
1048+
"accountId": "0991231",
1049+
"bucketId": "a4ba6a39d8b6b5fd561f0010",
1050+
"bucketName": "zsdfrtsazsdfafr",
1051+
"accountId": "4aa9865d6f00",
1052+
"bucketInfo": {},
1053+
"options": [],
1054+
"revision": 1,
1055+
"defaultServerSideEncryption": {
1056+
"isClientAuthorizedToRead" : true,
1057+
"value": {
1058+
"algorithm" : "AES256",
1059+
"mode" : "SSE-B2"
1060+
}
1061+
},
1062+
"fileLockConfiguration": {
1063+
"isClientAuthorizedToRead": true,
1064+
"value": {
1065+
"defaultRetention": {
1066+
"mode": null,
1067+
"period": null
1068+
},
1069+
"isFileLockEnabled": false
1070+
}
1071+
},
1072+
"replicationConfiguration": {
1073+
"clientIsAllowedToRead": true,
1074+
"value": {
1075+
"asReplicationSource": {
1076+
"replicationRules": [
1077+
{
1078+
"destinationBucketId": "c5f35d53a90a7ea284fb0719",
1079+
"fileNamePrefix": "",
1080+
"includeExistingFiles": True,
1081+
"isEnabled": true,
1082+
"priority": 1,
1083+
"replicationRuleName": "replication-us-west"
1084+
},
1085+
{
1086+
"destinationBucketId": "55f34d53a96a7ea284fb0719",
1087+
"fileNamePrefix": "",
1088+
"includeExistingFiles": True,
1089+
"isEnabled": true,
1090+
"priority": 2,
1091+
"replicationRuleName": "replication-us-west-2"
1092+
}
1093+
],
1094+
"sourceApplicationKeyId": "10053d55ae26b790000000006"
1095+
},
1096+
"asReplicationDestination": {
1097+
"sourceToDestinationKeyMapping": {
1098+
"10053d55ae26b790000000045": "10053d55ae26b790000000004",
1099+
"10053d55ae26b790000000046": "10053d55ae26b790030000004"
1100+
}
1101+
}
1102+
}
1103+
}
1104+
}
1105+
1106+
into a BucketStructure object.
1107+
1108+
:param dict bucket_dict: a dictionary with bucket properties
1109+
:rtype: BucketStructure
1110+
1111+
"""
1112+
type_ = bucket_dict.get('bucketType', ValueNotSet())
1113+
bucket_name = bucket_dict.get('bucketName', ValueNotSet())
1114+
bucket_id = bucket_dict.get('bucketId', ValueNotSet())
1115+
bucket_info = bucket_dict.get('bucketInfo', ValueNotSet())
1116+
cors_rules = bucket_dict.get('corsRules', ValueNotSet())
1117+
lifecycle_rules = bucket_dict.get('lifecycleRules', ValueNotSet())
1118+
revision = bucket_dict.get('revision', ValueNotSet())
1119+
options = set(bucket_dict['options']) if 'options' in bucket_dict else ValueNotSet()
1120+
account_id = bucket_dict.get('accountId', ValueNotSet())
1121+
1122+
default_server_side_encryption = (
1123+
EncryptionSettingFactory.from_bucket_dict(bucket_dict)
1124+
if EncryptionSettingFactory.TOP_LEVEL_KEY in bucket_dict else ValueNotSet()
1125+
)
1126+
replication = (
1127+
ReplicationConfigurationFactory.from_bucket_dict(bucket_dict).value
1128+
if ReplicationConfigurationFactory.TOP_LEVEL_KEY in bucket_dict else ValueNotSet()
1129+
)
1130+
1131+
if FileLockConfiguration.TOP_LEVEL_KEY in bucket_dict:
1132+
file_lock_configuration = FileLockConfiguration.from_bucket_dict(bucket_dict)
1133+
default_retention = file_lock_configuration.default_retention
1134+
is_file_lock_enabled = file_lock_configuration.is_file_lock_enabled
1135+
else:
1136+
default_retention = ValueNotSet()
1137+
is_file_lock_enabled = ValueNotSet()
1138+
1139+
return BucketStructure(
1140+
bucket_id,
1141+
bucket_name,
1142+
type_,
1143+
bucket_info,
1144+
cors_rules,
1145+
lifecycle_rules,
1146+
revision,
1147+
bucket_dict,
1148+
options,
1149+
default_server_side_encryption,
1150+
default_retention,
1151+
is_file_lock_enabled,
1152+
replication,
1153+
account_id=account_id,
1154+
)
1155+
9841156
@classmethod
9851157
def from_api_bucket_dict(cls, api, bucket_dict):
9861158
"""

b2sdk/encryption/setting.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ def __repr__(self):
220220

221221

222222
class EncryptionSettingFactory:
223+
TOP_LEVEL_KEY = 'defaultServerSideEncryption'
223224
# 2021-03-17: for the bucket the response of the server is:
224225
# if authorized to read:
225226
# "mode": "none"
@@ -301,7 +302,7 @@ def from_bucket_dict(cls, bucket_dict: dict) -> Optional[EncryptionSetting]:
301302
302303
"""
303304
default_sse = bucket_dict.get(
304-
'defaultServerSideEncryption',
305+
cls.TOP_LEVEL_KEY,
305306
{'isClientAuthorizedToRead': False},
306307
)
307308

b2sdk/file_lock.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,8 @@ def as_dict(self):
284284
}
285285
if self.period is not None:
286286
result['period'] = self.period.as_dict()
287+
else:
288+
result['period'] = None
287289
return result
288290

289291
def serialize_to_json_for_request(self):
@@ -301,6 +303,7 @@ def __repr__(self):
301303
class FileLockConfiguration:
302304
"""Represent bucket's file lock configuration, i.e. whether the file lock mechanism is enabled and default
303305
file retention"""
306+
TOP_LEVEL_KEY = 'fileLockConfiguration'
304307

305308
def __init__(
306309
self,
@@ -339,12 +342,12 @@ def from_bucket_dict(cls, bucket_dict):
339342
}
340343
"""
341344

342-
if not bucket_dict['fileLockConfiguration']['isClientAuthorizedToRead']:
345+
if not bucket_dict[cls.TOP_LEVEL_KEY]['isClientAuthorizedToRead']:
343346
return cls(UNKNOWN_BUCKET_RETENTION, None)
344347
retention = BucketRetentionSetting.from_bucket_retention_dict(
345-
bucket_dict['fileLockConfiguration']['value']['defaultRetention']
348+
bucket_dict[cls.TOP_LEVEL_KEY]['value']['defaultRetention']
346349
)
347-
is_file_lock_enabled = bucket_dict['fileLockConfiguration']['value']['isFileLockEnabled']
350+
is_file_lock_enabled = bucket_dict[cls.TOP_LEVEL_KEY]['value']['isFileLockEnabled']
348351
return cls(retention, is_file_lock_enabled)
349352

350353
def as_dict(self):

b2sdk/replication/setting.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ def from_dict(cls, value_dict: dict) -> 'ReplicationConfiguration':
220220

221221
@dataclass
222222
class ReplicationConfigurationFactory:
223+
TOP_LEVEL_KEY = 'replicationConfiguration'
223224
is_client_authorized_to_read: bool
224225
value: Optional[ReplicationConfiguration]
225226

@@ -229,7 +230,7 @@ def from_bucket_dict(cls, bucket_dict: dict) -> Optional['ReplicationConfigurati
229230
Returns ReplicationConfigurationFactory for the given bucket dict
230231
retrieved from the api, or None if no replication configured.
231232
"""
232-
replication_dict = bucket_dict.get('replicationConfiguration')
233+
replication_dict = bucket_dict.get(cls.TOP_LEVEL_KEY)
233234
if not replication_dict:
234235
return cls(
235236
is_client_authorized_to_read=True,

doc/source/api/bucket.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@ B2 Bucket
44
.. autoclass:: b2sdk.v2.Bucket()
55
:inherited-members:
66
:special-members: __init__
7+
8+
9+
.. autoclass:: b2sdk.v2.BucketStructure()
10+
:inherited-members:
11+
:special-members: __init__

0 commit comments

Comments
 (0)