Skip to content

Commit c43b7e7

Browse files
committed
Add 'Bucket.location_type' property. (#8089)
- Add named constants for allowed 'storage_class' values. - Un-deprecate STANDARD storage class. - Docs-deprecate storage classes implying location type. - Add 'Bucket.location_type' property. - Add named constants for allowed 'location_type' values.
1 parent 2d7aff4 commit c43b7e7

File tree

2 files changed

+188
-27
lines changed

2 files changed

+188
-27
lines changed

storage/google/cloud/storage/bucket.py

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -386,21 +386,79 @@ class Bucket(_PropertyMixin):
386386
This is used in Bucket.delete() and Bucket.make_public().
387387
"""
388388

389+
STANDARD_STORAGE_CLASS = "STANDARD"
390+
"""Storage class for objects accessed more than once per month."""
391+
392+
NEARLINE_STORAGE_CLASS = "NEARLINE"
393+
"""Storage class for objects accessed at most once per month."""
394+
395+
COLDLINE_STORAGE_CLASS = "COLDLINE"
396+
"""Storage class for objects accessed at most once per year."""
397+
398+
MULTI_REGIONAL_LEGACY_STORAGE_CLASS = "MULTI_REGIONAL"
399+
"""Legacy storage class.
400+
401+
Alias for :attr:`STANDARD_STORAGE_CLASS`.
402+
403+
Implies :attr:`MULTI_REGION_LOCATION_TYPE` for :attr:`location_type`.
404+
"""
405+
406+
REGIONAL_LEGACY_STORAGE_CLASS = "REGIONAL"
407+
"""Legacy storage class.
408+
409+
Alias for :attr:`STANDARD_STORAGE_CLASS`.
410+
411+
Implies :attr:`REGION_LOCATION_TYPE` for :attr:`location_type`.
412+
"""
413+
414+
DURABLE_REDUCED_AVAILABILITY_LEGACY_STORAGE_CLASS = "DURABLE_REDUCED_AVAILABILITY"
415+
"""Legacy storage class.
416+
417+
Similar to :attr:`NEARLINE_STORAGE_CLASS`.
418+
"""
419+
389420
_STORAGE_CLASSES = (
390-
"MULTI_REGIONAL",
391-
"REGIONAL",
392-
"NEARLINE",
393-
"COLDLINE",
394-
"STANDARD", # alias for MULTI_REGIONAL/REGIONAL, based on location
395-
"DURABLE_REDUCED_AVAILABILITY", # deprecated
421+
STANDARD_STORAGE_CLASS,
422+
NEARLINE_STORAGE_CLASS,
423+
COLDLINE_STORAGE_CLASS,
424+
MULTI_REGIONAL_LEGACY_STORAGE_CLASS, # deprecated
425+
REGIONAL_LEGACY_STORAGE_CLASS, # deprecated
426+
DURABLE_REDUCED_AVAILABILITY_LEGACY_STORAGE_CLASS, # deprecated
396427
)
397428
"""Allowed values for :attr:`storage_class`.
398429
430+
Default value is :attr:`STANDARD_STORAGE_CLASS`.
431+
399432
See
400433
https://cloud.google.com/storage/docs/json_api/v1/buckets#storageClass
401434
https://cloud.google.com/storage/docs/storage-classes
402435
"""
403436

437+
MULTI_REGION_LOCATION_TYPE = "multi-region"
438+
"""Location type: data will be replicated across regions in a multi-region.
439+
440+
Provides highest availability across largest area.
441+
"""
442+
443+
REGION_LOCATION_TYPE = "region"
444+
"""Location type: data will be stored within a single region.
445+
446+
Provides lowest latency within a single region.
447+
"""
448+
449+
DUAL_REGION_LOCATION_TYPE = "dual-region"
450+
"""Location type: data will be stored within two primary regions.
451+
452+
Provides high availability and low latency across two regions.
453+
"""
454+
455+
_LOCATION_TYPES = (
456+
MULTI_REGION_LOCATION_TYPE,
457+
REGION_LOCATION_TYPE,
458+
DUAL_REGION_LOCATION_TYPE,
459+
)
460+
"""Allowed values for :attr:`location_type`."""
461+
404462
def __init__(self, client, name=None, user_project=None):
405463
name = _validate_name(name)
406464
super(Bucket, self).__init__(name=name)
@@ -1377,6 +1435,38 @@ def location(self, value):
13771435
warnings.warn(_LOCATION_SETTER_MESSAGE, DeprecationWarning, stacklevel=2)
13781436
self._location = value
13791437

1438+
@property
1439+
def location_type(self):
1440+
"""Retrieve or set the location type for the bucket.
1441+
1442+
See https://cloud.google.com/storage/docs/storage-classes
1443+
1444+
:setter: Set the location type for this bucket.
1445+
:getter: Gets the the location type for this bucket.
1446+
1447+
:rtype: str or ``NoneType``
1448+
:returns:
1449+
If set, one of :attr:`MULTI_REGION_LOCATION_TYPE`,
1450+
:attr:`REGION_LOCATION_TYPE`, or :attr:`DUAL_REGION_LOCATION_TYPE`,
1451+
else ``None``.
1452+
"""
1453+
return self._properties.get("locationType")
1454+
1455+
@location_type.setter
1456+
def location_type(self, value):
1457+
"""Set the location type for the bucket.
1458+
1459+
See https://cloud.google.com/storage/docs/storage-classes
1460+
1461+
:type value: str
1462+
:param value:
1463+
One of :attr:`MULTI_REGION_LOCATION_TYPE`,
1464+
:attr:`REGION_LOCATION_TYPE`, or :attr:`DUAL_REGION_LOCATION_TYPE`,
1465+
"""
1466+
if value not in self._LOCATION_TYPES:
1467+
raise ValueError("Invalid location type: %s" % (value,))
1468+
self._patch_property("locationType", value)
1469+
13801470
def get_logging(self):
13811471
"""Return info about access logging for this bucket.
13821472
@@ -1533,9 +1623,14 @@ def storage_class(self):
15331623
:getter: Gets the the storage class for this bucket.
15341624
15351625
:rtype: str or ``NoneType``
1536-
:returns: If set, one of "MULTI_REGIONAL", "REGIONAL",
1537-
"NEARLINE", "COLDLINE", "STANDARD", or
1538-
"DURABLE_REDUCED_AVAILABILITY", else ``None``.
1626+
:returns:
1627+
If set, one of :attr:`NEARLINE_STORAGE_CLASS`,
1628+
:attr:`COLDLINE_STORAGE_CLASS`, :attr:`STANDARD_STORAGE_CLASS`,
1629+
:attr:`MULTI_REGIONAL_LEGACY_STORAGE_CLASS`,
1630+
:attr:`REGIONAL_LEGACY_STORAGE_CLASS`,
1631+
or
1632+
:attr:`DURABLE_REDUCED_AVAILABILITY_LEGACY_STORAGE_CLASS`,
1633+
else ``None``.
15391634
"""
15401635
return self._properties.get("storageClass")
15411636

@@ -1546,8 +1641,13 @@ def storage_class(self, value):
15461641
See https://cloud.google.com/storage/docs/storage-classes
15471642
15481643
:type value: str
1549-
:param value: one of "MULTI_REGIONAL", "REGIONAL", "NEARLINE",
1550-
"COLDLINE", "STANDARD", or "DURABLE_REDUCED_AVAILABILITY"
1644+
:param value:
1645+
One of :attr:`NEARLINE_STORAGE_CLASS`,
1646+
:attr:`COLDLINE_STORAGE_CLASS`, :attr:`STANDARD_STORAGE_CLASS`,
1647+
:attr:`MULTI_REGIONAL_LEGACY_STORAGE_CLASS`,
1648+
:attr:`REGIONAL_LEGACY_STORAGE_CLASS`,
1649+
or
1650+
:attr:`DURABLE_REDUCED_AVAILABILITY_LEGACY_STORAGE_CLASS`,
15511651
"""
15521652
if value not in self._STORAGE_CLASSES:
15531653
raise ValueError("Invalid storage class: %s" % (value,))

storage/tests/unit/test_bucket.py

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -299,16 +299,23 @@ def _make_one(self, client=None, name=None, properties=None, user_project=None):
299299
bucket._properties = properties or {}
300300
return bucket
301301

302+
def test_ctor_w_invalid_name(self):
303+
NAME = "#invalid"
304+
with self.assertRaises(ValueError):
305+
self._make_one(name=NAME)
306+
302307
def test_ctor(self):
303308
NAME = "name"
304309
properties = {"key": "value"}
305310
bucket = self._make_one(name=NAME, properties=properties)
306311
self.assertEqual(bucket.name, NAME)
307312
self.assertEqual(bucket._properties, properties)
313+
self.assertEqual(list(bucket._changes), [])
308314
self.assertFalse(bucket._acl.loaded)
309315
self.assertIs(bucket._acl.bucket, bucket)
310316
self.assertFalse(bucket._default_object_acl.loaded)
311317
self.assertIs(bucket._default_object_acl.bucket, bucket)
318+
self.assertEqual(list(bucket._label_removals), [])
312319
self.assertIsNone(bucket.user_project)
313320

314321
def test_ctor_w_user_project(self):
@@ -319,11 +326,13 @@ def test_ctor_w_user_project(self):
319326
bucket = self._make_one(client, name=NAME, user_project=USER_PROJECT)
320327
self.assertEqual(bucket.name, NAME)
321328
self.assertEqual(bucket._properties, {})
322-
self.assertEqual(bucket.user_project, USER_PROJECT)
329+
self.assertEqual(list(bucket._changes), [])
323330
self.assertFalse(bucket._acl.loaded)
324331
self.assertIs(bucket._acl.bucket, bucket)
325332
self.assertFalse(bucket._default_object_acl.loaded)
326333
self.assertIs(bucket._default_object_acl.bucket, bucket)
334+
self.assertEqual(list(bucket._label_removals), [])
335+
self.assertEqual(bucket.user_project, USER_PROJECT)
327336

328337
def test_blob_wo_keys(self):
329338
from google.cloud.storage.blob import Blob
@@ -1496,6 +1505,47 @@ def test_labels_setter_with_removal(self):
14961505
_, _, kwargs = client._connection.api_request.mock_calls[0]
14971506
self.assertNotIn("labels", kwargs["data"])
14981507

1508+
def test_location_type_getter_unset(self):
1509+
bucket = self._make_one()
1510+
self.assertIsNone(bucket.location_type)
1511+
1512+
def test_location_type_getter_set(self):
1513+
klass = self._get_target_class()
1514+
properties = {"locationType": klass.REGION_LOCATION_TYPE}
1515+
bucket = self._make_one(properties=properties)
1516+
self.assertEqual(bucket.location_type, klass.REGION_LOCATION_TYPE)
1517+
1518+
def test_location_type_setter_invalid(self):
1519+
NAME = "name"
1520+
bucket = self._make_one(name=NAME)
1521+
with self.assertRaises(ValueError):
1522+
bucket.location_type = "bogus"
1523+
self.assertFalse("locationType" in bucket._changes)
1524+
1525+
def test_location_type_setter_MULTI_REGION(self):
1526+
klass = self._get_target_class()
1527+
NAME = "name"
1528+
bucket = self._make_one(name=NAME)
1529+
bucket.location_type = klass.MULTI_REGION_LOCATION_TYPE
1530+
self.assertEqual(bucket.location_type, klass.MULTI_REGION_LOCATION_TYPE)
1531+
self.assertTrue("locationType" in bucket._changes)
1532+
1533+
def test_location_type_setter_REGION(self):
1534+
klass = self._get_target_class()
1535+
NAME = "name"
1536+
bucket = self._make_one(name=NAME)
1537+
bucket.location_type = klass.REGION_LOCATION_TYPE
1538+
self.assertEqual(bucket.location_type, klass.REGION_LOCATION_TYPE)
1539+
self.assertTrue("locationType" in bucket._changes)
1540+
1541+
def test_location_type_setter_DUAL_REGION(self):
1542+
klass = self._get_target_class()
1543+
NAME = "name"
1544+
bucket = self._make_one(name=NAME)
1545+
bucket.location_type = klass.DUAL_REGION_LOCATION_TYPE
1546+
self.assertEqual(bucket.location_type, klass.DUAL_REGION_LOCATION_TYPE)
1547+
self.assertTrue("locationType" in bucket._changes)
1548+
14991549
def test_get_logging_w_prefix(self):
15001550
NAME = "name"
15011551
LOG_BUCKET = "logs"
@@ -1658,10 +1708,10 @@ def test_self_link(self):
16581708
self.assertEqual(bucket.self_link, SELF_LINK)
16591709

16601710
def test_storage_class_getter(self):
1661-
STORAGE_CLASS = "http://example.com/self/"
1662-
properties = {"storageClass": STORAGE_CLASS}
1711+
klass = self._get_target_class()
1712+
properties = {"storageClass": klass.NEARLINE_STORAGE_CLASS}
16631713
bucket = self._make_one(properties=properties)
1664-
self.assertEqual(bucket.storage_class, STORAGE_CLASS)
1714+
self.assertEqual(bucket.storage_class, klass.NEARLINE_STORAGE_CLASS)
16651715

16661716
def test_storage_class_setter_invalid(self):
16671717
NAME = "name"
@@ -1671,45 +1721,56 @@ def test_storage_class_setter_invalid(self):
16711721
self.assertFalse("storageClass" in bucket._changes)
16721722

16731723
def test_storage_class_setter_STANDARD(self):
1724+
klass = self._get_target_class()
16741725
NAME = "name"
16751726
bucket = self._make_one(name=NAME)
1676-
bucket.storage_class = "STANDARD"
1677-
self.assertEqual(bucket.storage_class, "STANDARD")
1727+
bucket.storage_class = klass.STANDARD_STORAGE_CLASS
1728+
self.assertEqual(bucket.storage_class, klass.STANDARD_STORAGE_CLASS)
16781729
self.assertTrue("storageClass" in bucket._changes)
16791730

16801731
def test_storage_class_setter_NEARLINE(self):
1732+
klass = self._get_target_class()
16811733
NAME = "name"
16821734
bucket = self._make_one(name=NAME)
1683-
bucket.storage_class = "NEARLINE"
1684-
self.assertEqual(bucket.storage_class, "NEARLINE")
1735+
bucket.storage_class = klass.NEARLINE_STORAGE_CLASS
1736+
self.assertEqual(bucket.storage_class, klass.NEARLINE_STORAGE_CLASS)
16851737
self.assertTrue("storageClass" in bucket._changes)
16861738

16871739
def test_storage_class_setter_COLDLINE(self):
1740+
klass = self._get_target_class()
16881741
NAME = "name"
16891742
bucket = self._make_one(name=NAME)
1690-
bucket.storage_class = "COLDLINE"
1691-
self.assertEqual(bucket.storage_class, "COLDLINE")
1743+
bucket.storage_class = klass.COLDLINE_STORAGE_CLASS
1744+
self.assertEqual(bucket.storage_class, klass.COLDLINE_STORAGE_CLASS)
16921745
self.assertTrue("storageClass" in bucket._changes)
16931746

16941747
def test_storage_class_setter_MULTI_REGIONAL(self):
1748+
klass = self._get_target_class()
16951749
NAME = "name"
16961750
bucket = self._make_one(name=NAME)
1697-
bucket.storage_class = "MULTI_REGIONAL"
1698-
self.assertEqual(bucket.storage_class, "MULTI_REGIONAL")
1751+
bucket.storage_class = klass.MULTI_REGIONAL_LEGACY_STORAGE_CLASS
1752+
self.assertEqual(
1753+
bucket.storage_class, klass.MULTI_REGIONAL_LEGACY_STORAGE_CLASS
1754+
)
16991755
self.assertTrue("storageClass" in bucket._changes)
17001756

17011757
def test_storage_class_setter_REGIONAL(self):
1758+
klass = self._get_target_class()
17021759
NAME = "name"
17031760
bucket = self._make_one(name=NAME)
1704-
bucket.storage_class = "REGIONAL"
1705-
self.assertEqual(bucket.storage_class, "REGIONAL")
1761+
bucket.storage_class = klass.REGIONAL_LEGACY_STORAGE_CLASS
1762+
self.assertEqual(bucket.storage_class, klass.REGIONAL_LEGACY_STORAGE_CLASS)
17061763
self.assertTrue("storageClass" in bucket._changes)
17071764

17081765
def test_storage_class_setter_DURABLE_REDUCED_AVAILABILITY(self):
1766+
klass = self._get_target_class()
17091767
NAME = "name"
17101768
bucket = self._make_one(name=NAME)
1711-
bucket.storage_class = "DURABLE_REDUCED_AVAILABILITY"
1712-
self.assertEqual(bucket.storage_class, "DURABLE_REDUCED_AVAILABILITY")
1769+
bucket.storage_class = klass.DURABLE_REDUCED_AVAILABILITY_LEGACY_STORAGE_CLASS
1770+
self.assertEqual(
1771+
bucket.storage_class,
1772+
klass.DURABLE_REDUCED_AVAILABILITY_LEGACY_STORAGE_CLASS,
1773+
)
17131774
self.assertTrue("storageClass" in bucket._changes)
17141775

17151776
def test_time_created(self):

0 commit comments

Comments
 (0)