Skip to content
Permalink
Browse files
feat(storage): add support of daysSinceNoncurrentTime and noncurrentT…
…imeBefore (#162)

* feat(storage): add support of daysSinceNoncurrentTime and noncurrentTimeBefore

* feat(storage): fix code coverage

* feat(storage): add custom method to convert datetime to string

* feat(storage): remove custom method as server support microsec

* feat(storage): change the return type of noncurrent_time_before

* feat(storage): change non_current_time type from datetime to date

* feat: nit

Co-authored-by: Jonathan Lui <jonathanlui@google.com>
Co-authored-by: Frank Natividad <frankyn@users.noreply.github.com>
  • Loading branch information
3 people committed Aug 25, 2020
1 parent 413f7b5 commit 136c0970f8ef7ad4751104e3b8b7dd3204220a67
Showing with 90 additions and 4 deletions.
  1. +35 −2 google/cloud/storage/bucket.py
  2. +15 −2 tests/system/test_system.py
  3. +40 −0 tests/unit/test_bucket.py
@@ -148,7 +148,7 @@ class LifecycleRuleConditions(dict):
See: https://cloud.google.com/storage/docs/lifecycle
:type age: int
:param age: (Optional) Apply rule action to items whos age, in days,
:param age: (Optional) Apply rule action to items whose age, in days,
exceeds this value.
:type created_before: datetime.date
@@ -170,6 +170,19 @@ class LifecycleRuleConditions(dict):
:param number_of_newer_versions: (Optional) Apply rule action to versioned
items having N newer versions.
:type days_since_noncurrent_time: int
:param days_since_noncurrent_time: (Optional) Apply rule action to items whose number of days
elapsed since the non current timestamp. This condition
is relevant only for versioned objects. The value of the field
must be a non negative integer. If it's zero, the object version
will become eligible for lifecycle action as soon as it becomes
non current.
:type noncurrent_time_before: :class:`datetime.date`
:param noncurrent_time_before: (Optional) Date object parsed from RFC3339 valid date, apply
rule action to items whose non current time is before this date.
This condition is relevant only for versioned objects, e.g, 2019-03-16.
:raises ValueError: if no arguments are passed.
"""

@@ -180,6 +193,8 @@ def __init__(
is_live=None,
matches_storage_class=None,
number_of_newer_versions=None,
days_since_noncurrent_time=None,
noncurrent_time_before=None,
_factory=False,
):
conditions = {}
@@ -202,6 +217,12 @@ def __init__(
if not _factory and not conditions:
raise ValueError("Supply at least one condition")

if days_since_noncurrent_time is not None:
conditions["daysSinceNoncurrentTime"] = days_since_noncurrent_time

if noncurrent_time_before is not None:
conditions["noncurrentTimeBefore"] = noncurrent_time_before.isoformat()

super(LifecycleRuleConditions, self).__init__(conditions)

@classmethod
@@ -245,6 +266,18 @@ def number_of_newer_versions(self):
"""Conditon's 'number_of_newer_versions' value."""
return self.get("numNewerVersions")

@property
def days_since_noncurrent_time(self):
"""Conditon's 'days_since_noncurrent_time' value."""
return self.get("daysSinceNoncurrentTime")

@property
def noncurrent_time_before(self):
"""Conditon's 'noncurrent_time_before' value."""
before = self.get("noncurrentTimeBefore")
if before is not None:
return datetime_helpers.from_iso8601_date(before)


class LifecycleRuleDelete(dict):
"""Map a lifecycle rule deleting matching items.
@@ -274,7 +307,7 @@ def from_api_repr(cls, resource):


class LifecycleRuleSetStorageClass(dict):
"""Map a lifecycle rule upating storage class of matching items.
"""Map a lifecycle rule updating storage class of matching items.
:type storage_class: str, one of :attr:`Bucket.STORAGE_CLASSES`.
:param storage_class: new storage class to assign to matching items.
@@ -192,22 +192,35 @@ def test_bucket_create_w_alt_storage_class(self):
self.assertEqual(created.storage_class, constants.ARCHIVE_STORAGE_CLASS)

def test_lifecycle_rules(self):
import datetime
from google.cloud.storage import constants

new_bucket_name = "w-lifcycle-rules" + unique_resource_id("-")
noncurrent_before = datetime.date(2018, 8, 1)
self.assertRaises(
exceptions.NotFound, Config.CLIENT.get_bucket, new_bucket_name
)
bucket = Config.CLIENT.bucket(new_bucket_name)
bucket.add_lifecycle_delete_rule(age=42)
bucket.add_lifecycle_delete_rule(
age=42,
number_of_newer_versions=3,
days_since_noncurrent_time=2,
noncurrent_time_before=noncurrent_before,
)

bucket.add_lifecycle_set_storage_class_rule(
constants.COLDLINE_STORAGE_CLASS,
is_live=False,
matches_storage_class=[constants.NEARLINE_STORAGE_CLASS],
)

expected_rules = [
LifecycleRuleDelete(age=42),
LifecycleRuleDelete(
age=42,
number_of_newer_versions=3,
days_since_noncurrent_time=2,
noncurrent_time_before=noncurrent_before,
),
LifecycleRuleSetStorageClass(
constants.COLDLINE_STORAGE_CLASS,
is_live=False,
@@ -77,6 +77,7 @@ def test_ctor_w_created_before_and_is_live(self):
self.assertEqual(conditions.is_live, False)
self.assertIsNone(conditions.matches_storage_class)
self.assertIsNone(conditions.number_of_newer_versions)
self.assertIsNone(conditions.noncurrent_time_before)

def test_ctor_w_number_of_newer_versions(self):
conditions = self._make_one(number_of_newer_versions=3)
@@ -88,24 +89,63 @@ def test_ctor_w_number_of_newer_versions(self):
self.assertIsNone(conditions.matches_storage_class)
self.assertEqual(conditions.number_of_newer_versions, 3)

def test_ctor_w_days_since_noncurrent_time(self):
conditions = self._make_one(
number_of_newer_versions=3, days_since_noncurrent_time=2
)
expected = {"numNewerVersions": 3, "daysSinceNoncurrentTime": 2}
self.assertEqual(dict(conditions), expected)
self.assertIsNone(conditions.age)
self.assertIsNone(conditions.created_before)
self.assertIsNone(conditions.is_live)
self.assertIsNone(conditions.matches_storage_class)
self.assertEqual(conditions.number_of_newer_versions, 3)
self.assertEqual(conditions.days_since_noncurrent_time, 2)

def test_ctor_w_noncurrent_time_before(self):
import datetime

noncurrent_before = datetime.date(2018, 8, 1)
conditions = self._make_one(
number_of_newer_versions=3, noncurrent_time_before=noncurrent_before
)

expected = {
"numNewerVersions": 3,
"noncurrentTimeBefore": noncurrent_before.isoformat(),
}
self.assertEqual(dict(conditions), expected)
self.assertIsNone(conditions.age)
self.assertIsNone(conditions.created_before)
self.assertIsNone(conditions.is_live)
self.assertIsNone(conditions.matches_storage_class)
self.assertEqual(conditions.number_of_newer_versions, 3)
self.assertEqual(conditions.noncurrent_time_before, noncurrent_before)

def test_from_api_repr(self):
import datetime

noncurrent_before = datetime.date(2018, 8, 1)
before = datetime.date(2018, 8, 1)
klass = self._get_target_class()

resource = {
"age": 10,
"createdBefore": "2018-08-01",
"isLive": True,
"matchesStorageClass": ["COLDLINE"],
"numNewerVersions": 3,
"daysSinceNoncurrentTime": 2,
"noncurrentTimeBefore": noncurrent_before.isoformat(),
}
conditions = klass.from_api_repr(resource)
self.assertEqual(conditions.age, 10)
self.assertEqual(conditions.created_before, before)
self.assertEqual(conditions.is_live, True)
self.assertEqual(conditions.matches_storage_class, ["COLDLINE"])
self.assertEqual(conditions.number_of_newer_versions, 3)
self.assertEqual(conditions.days_since_noncurrent_time, 2)
self.assertEqual(conditions.noncurrent_time_before, noncurrent_before)


class Test_LifecycleRuleDelete(unittest.TestCase):

0 comments on commit 136c097

Please sign in to comment.