Skip to content

Commit c29729e

Browse files
committed
Add blob properties to support retention policy feature. (#446)
Toward #445.
1 parent 6bad253 commit c29729e

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
@@ -1590,6 +1590,16 @@ def etag(self):
15901590
"""
15911591
return self._properties.get('etag')
15921592

1593+
event_based_hold = _scalar_property('eventBasedHold')
1594+
"""Is an event-based hold active on the object?
1595+
1596+
See `API reference docs`_.
1597+
1598+
If the property is not set locally, returns :data:`None`.
1599+
1600+
:rtype: bool or ``NoneType``
1601+
"""
1602+
15931603
@property
15941604
def generation(self):
15951605
"""Retrieve the generation for the object.
@@ -1696,6 +1706,20 @@ def owner(self):
16961706
"""
16971707
return copy.deepcopy(self._properties.get('owner'))
16981708

1709+
@property
1710+
def retention_expiration_time(self):
1711+
"""Retrieve timestamp at which the object's retention period expires.
1712+
1713+
See https://cloud.google.com/storage/docs/json_api/v1/objects
1714+
1715+
:rtype: :class:`datetime.datetime` or ``NoneType``
1716+
:returns: Datetime object parsed from RFC3339 valid timestamp, or
1717+
``None`` if the property is not set locally.
1718+
"""
1719+
value = self._properties.get('retentionExpirationTime')
1720+
if value is not None:
1721+
return _rfc3339_to_datetime(value)
1722+
16991723
@property
17001724
def self_link(self):
17011725
"""Retrieve the URI for the object.
@@ -1749,6 +1773,16 @@ def kms_key_name(self):
17491773
"DURABLE_REDUCED_AVAILABILITY", else ``None``.
17501774
"""
17511775

1776+
temporary_hold = _scalar_property('temporaryHold')
1777+
"""Is a temporary hold active on the object?
1778+
1779+
See `API reference docs`_.
1780+
1781+
If the property is not set locally, returns :data:`None`.
1782+
1783+
:rtype: bool or ``NoneType``
1784+
"""
1785+
17521786
@property
17531787
def time_deleted(self):
17541788
"""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
@@ -2667,6 +2667,35 @@ def test_etag(self):
26672667
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
26682668
self.assertEqual(blob.etag, ETAG)
26692669

2670+
def test_event_based_hold_getter_missing(self):
2671+
BLOB_NAME = 'blob-name'
2672+
bucket = _Bucket()
2673+
properties = {}
2674+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2675+
self.assertIsNone(blob.event_based_hold)
2676+
2677+
def test_event_based_hold_getter_false(self):
2678+
BLOB_NAME = 'blob-name'
2679+
bucket = _Bucket()
2680+
properties = {'eventBasedHold': False}
2681+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2682+
self.assertFalse(blob.event_based_hold)
2683+
2684+
def test_event_based_hold_getter_true(self):
2685+
BLOB_NAME = 'blob-name'
2686+
bucket = _Bucket()
2687+
properties = {'eventBasedHold': True}
2688+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2689+
self.assertTrue(blob.event_based_hold)
2690+
2691+
def test_event_based_hold_setter(self):
2692+
BLOB_NAME = 'blob-name'
2693+
bucket = _Bucket()
2694+
blob = self._make_one(BLOB_NAME, bucket=bucket)
2695+
self.assertIsNone(blob.event_based_hold)
2696+
blob.event_based_hold = True
2697+
self.assertEqual(blob.event_based_hold, True)
2698+
26702699
def test_generation(self):
26712700
BUCKET = object()
26722701
GENERATION = 42
@@ -2766,6 +2795,23 @@ def test_owner(self):
27662795
self.assertEqual(owner['entity'], 'project-owner-12345')
27672796
self.assertEqual(owner['entityId'], '23456')
27682797

2798+
def test_retention_expiration_time(self):
2799+
from google.cloud._helpers import _RFC3339_MICROS
2800+
from google.cloud._helpers import UTC
2801+
2802+
BLOB_NAME = 'blob-name'
2803+
bucket = _Bucket()
2804+
TIMESTAMP = datetime.datetime(2014, 11, 5, 20, 34, 37, tzinfo=UTC)
2805+
TIME_CREATED = TIMESTAMP.strftime(_RFC3339_MICROS)
2806+
properties = {'retentionExpirationTime': TIME_CREATED}
2807+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2808+
self.assertEqual(blob.retention_expiration_time, TIMESTAMP)
2809+
2810+
def test_retention_expiration_time_unset(self):
2811+
BUCKET = object()
2812+
blob = self._make_one('blob-name', bucket=BUCKET)
2813+
self.assertIsNone(blob.retention_expiration_time)
2814+
27692815
def test_self_link(self):
27702816
BLOB_NAME = 'blob-name'
27712817
bucket = _Bucket()
@@ -2811,6 +2857,35 @@ def test_storage_class_setter(self):
28112857
self.assertEqual(blob.storage_class, storage_class)
28122858
self.assertEqual(blob._properties, {'storageClass': storage_class})
28132859

2860+
def test_temporary_hold_getter_missing(self):
2861+
BLOB_NAME = 'blob-name'
2862+
bucket = _Bucket()
2863+
properties = {}
2864+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2865+
self.assertIsNone(blob.temporary_hold)
2866+
2867+
def test_temporary_hold_getter_false(self):
2868+
BLOB_NAME = 'blob-name'
2869+
bucket = _Bucket()
2870+
properties = {'temporaryHold': False}
2871+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2872+
self.assertFalse(blob.temporary_hold)
2873+
2874+
def test_temporary_hold_getter_true(self):
2875+
BLOB_NAME = 'blob-name'
2876+
bucket = _Bucket()
2877+
properties = {'temporaryHold': True}
2878+
blob = self._make_one(BLOB_NAME, bucket=bucket, properties=properties)
2879+
self.assertTrue(blob.temporary_hold)
2880+
2881+
def test_temporary_hold_setter(self):
2882+
BLOB_NAME = 'blob-name'
2883+
bucket = _Bucket()
2884+
blob = self._make_one(BLOB_NAME, bucket=bucket)
2885+
self.assertIsNone(blob.temporary_hold)
2886+
blob.temporary_hold = True
2887+
self.assertEqual(blob.temporary_hold, True)
2888+
28142889
def test_time_deleted(self):
28152890
from google.cloud._helpers import _RFC3339_MICROS
28162891
from google.cloud._helpers import UTC

0 commit comments

Comments
 (0)