Skip to content

Commit 6b87cee

Browse files
BucketStructure introduced
1 parent 002dd22 commit 6b87cee

File tree

10 files changed

+299
-26
lines changed

10 files changed

+299
-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: 185 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,31 @@
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+
"""Sentry class for signifying no value for a property was supplied"""
56+
pass
5557

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

5877
def __init__(
5978
self,
60-
api,
6179
id_,
6280
name=None,
6381
type_=None,
@@ -73,9 +91,10 @@ def __init__(
7391
default_retention: BucketRetentionSetting = UNKNOWN_BUCKET_RETENTION,
7492
is_file_lock_enabled: Optional[bool] = None,
7593
replication: Optional[ReplicationConfiguration] = None,
94+
*,
95+
account_id,
7696
):
7797
"""
78-
:param b2sdk.v2.B2Api api: an API object
7998
:param str id_: a bucket id
8099
:param str name: a bucket name
81100
:param str type_: a bucket type
@@ -89,9 +108,10 @@ def __init__(
89108
:param b2sdk.v2.BucketRetentionSetting default_retention: default retention setting
90109
:param bool is_file_lock_enabled: whether file locking is enabled or not
91110
:param b2sdk.v2.ReplicationConfiguration replication: replication rules for the bucket
111+
:param str account_id: id of the account owning the bucket
92112
"""
93-
self.api = api
94113
self.id_ = id_
114+
self.account_id = account_id
95115
self.name = name
96116
self.type_ = type_
97117
self.bucket_info = bucket_info or {}
@@ -105,6 +125,45 @@ def __init__(
105125
self.is_file_lock_enabled = is_file_lock_enabled
106126
self.replication = replication
107127

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

9611020
return result
9621021

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

9671023
class BucketFactory:
9681024
"""
@@ -981,6 +1037,123 @@ def from_api_response(cls, api, response):
9811037
"""
9821038
return [cls.from_api_bucket_dict(api, bucket_dict) for bucket_dict in response['buckets']]
9831039

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

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,11 @@ 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__
12+
13+
14+
.. autoclass:: b2sdk.v2.ValueNotSet()

0 commit comments

Comments
 (0)