Skip to content

Commit f6048da

Browse files
committed
Add blob properties to support retention policy feature. (#446)
Toward #445.
1 parent 55a3b20 commit f6048da

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed

storage/google/cloud/storage/blob.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,16 @@ def etag(self):
15961596
"""
15971597
return self._properties.get('etag')
15981598

1599+
event_based_hold = _scalar_property('eventBasedHold')
1600+
"""Is an event-based hold active on the object?
1601+
1602+
See `API reference docs`_.
1603+
1604+
If the property is not set locally, returns :data:`None`.
1605+
1606+
:rtype: bool or ``NoneType``
1607+
"""
1608+
15991609
@property
16001610
def generation(self):
16011611
"""Retrieve the generation for the object.
@@ -1700,6 +1710,20 @@ def owner(self):
17001710
"""
17011711
return copy.deepcopy(self._properties.get('owner'))
17021712

1713+
@property
1714+
def retention_expiration_time(self):
1715+
"""Retrieve timestamp at which the object's retention period expires.
1716+
1717+
See https://cloud.google.com/storage/docs/json_api/v1/objects
1718+
1719+
:rtype: :class:`datetime.datetime` or ``NoneType``
1720+
:returns: Datetime object parsed from RFC3339 valid timestamp, or
1721+
``None`` if the property is not set locally.
1722+
"""
1723+
value = self._properties.get('retentionExpirationTime')
1724+
if value is not None:
1725+
return _rfc3339_to_datetime(value)
1726+
17031727
@property
17041728
def self_link(self):
17051729
"""Retrieve the URI for the object.
@@ -1752,6 +1776,16 @@ def kms_key_name(self):
17521776
"DURABLE_REDUCED_AVAILABILITY", else ``None``.
17531777
"""
17541778

1779+
temporary_hold = _scalar_property('temporaryHold')
1780+
"""Is a temporary hold active on the object?
1781+
1782+
See `API reference docs`_.
1783+
1784+
If the property is not set locally, returns :data:`None`.
1785+
1786+
:rtype: bool or ``NoneType``
1787+
"""
1788+
17551789
@property
17561790
def time_deleted(self):
17571791
"""Retrieve the timestamp at which the object was deleted.

storage/tests/unit/test_blob.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2638,6 +2638,35 @@ def test_etag(self):
26382638
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
26392639
self.assertEqual(blob.etag, ETAG)
26402640

2641+
def test_event_based_hold_getter_missing(self):
2642+
BLOB_NAME = 'blob-name'
2643+
bucket = _Bucket()
2644+
properties = {}
2645+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2646+
self.assertIsNone(blob.event_based_hold)
2647+
2648+
def test_event_based_hold_getter_false(self):
2649+
BLOB_NAME = 'blob-name'
2650+
bucket = _Bucket()
2651+
properties = {'eventBasedHold': False}
2652+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2653+
self.assertFalse(blob.event_based_hold)
2654+
2655+
def test_event_based_hold_getter_true(self):
2656+
BLOB_NAME = 'blob-name'
2657+
bucket = _Bucket()
2658+
properties = {'eventBasedHold': True}
2659+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2660+
self.assertTrue(blob.event_based_hold)
2661+
2662+
def test_event_based_hold_setter(self):
2663+
BLOB_NAME = 'blob-name'
2664+
bucket = _Bucket()
2665+
blob = self._make_one(BLOB_NAME, bucket=bucket)
2666+
self.assertIsNone(blob.event_based_hold)
2667+
blob.event_based_hold = True
2668+
self.assertEqual(blob.event_based_hold, True)
2669+
26412670
def test_generation(self):
26422671
BUCKET = object()
26432672
GENERATION = 42
@@ -2737,6 +2766,23 @@ def test_owner(self):
27372766
self.assertEqual(owner['entity'], 'project-owner-12345')
27382767
self.assertEqual(owner['entityId'], '23456')
27392768

2769+
def test_retention_expiration_time(self):
2770+
from google.cloud._helpers import _RFC3339_MICROS
2771+
from google.cloud._helpers import UTC
2772+
2773+
BLOB_NAME = 'blob-name'
2774+
bucket = _Bucket()
2775+
TIMESTAMP = datetime.datetime(2014, 11, 5, 20, 34, 37, tzinfo=UTC)
2776+
TIME_CREATED = TIMESTAMP.strftime(_RFC3339_MICROS)
2777+
properties = {'retentionExpirationTime': TIME_CREATED}
2778+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2779+
self.assertEqual(blob.retention_expiration_time, TIMESTAMP)
2780+
2781+
def test_retention_expiration_time_unset(self):
2782+
BUCKET = object()
2783+
blob = self._make_one('blob-name', bucket=BUCKET)
2784+
self.assertIsNone(blob.retention_expiration_time)
2785+
27402786
def test_self_link(self):
27412787
BLOB_NAME = 'blob-name'
27422788
bucket = _Bucket()
@@ -2782,6 +2828,35 @@ def test_storage_class_setter(self):
27822828
self.assertEqual(blob.storage_class, storage_class)
27832829
self.assertEqual(blob._properties, {'storageClass': storage_class})
27842830

2831+
def test_temporary_hold_getter_missing(self):
2832+
BLOB_NAME = 'blob-name'
2833+
bucket = _Bucket()
2834+
properties = {}
2835+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2836+
self.assertIsNone(blob.temporary_hold)
2837+
2838+
def test_temporary_hold_getter_false(self):
2839+
BLOB_NAME = 'blob-name'
2840+
bucket = _Bucket()
2841+
properties = {'temporaryHold': False}
2842+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2843+
self.assertFalse(blob.temporary_hold)
2844+
2845+
def test_temporary_hold_getter_true(self):
2846+
BLOB_NAME = 'blob-name'
2847+
bucket = _Bucket()
2848+
properties = {'temporaryHold': True}
2849+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2850+
self.assertTrue(blob.temporary_hold)
2851+
2852+
def test_temporary_hold_setter(self):
2853+
BLOB_NAME = 'blob-name'
2854+
bucket = _Bucket()
2855+
blob = self._make_one(BLOB_NAME, bucket=bucket)
2856+
self.assertIsNone(blob.temporary_hold)
2857+
blob.temporary_hold = True
2858+
self.assertEqual(blob.temporary_hold, True)
2859+
27852860
def test_time_deleted(self):
27862861
from google.cloud._helpers import _RFC3339_MICROS
27872862
from google.cloud._helpers import UTC

0 commit comments

Comments
 (0)