Skip to content

Commit

Permalink
feat: Add setting disposition_at field for files under retention (#710
Browse files Browse the repository at this point in the history
)

Closes: SDK-1921
  • Loading branch information
lukaszsocha2 committed Mar 10, 2022
1 parent 426b2c5 commit 91b1373
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 1 deletion.
16 changes: 16 additions & 0 deletions boxsdk/object/file.py
@@ -1,7 +1,9 @@
import json
import os
from datetime import datetime
from typing import TYPE_CHECKING, Optional, Tuple, Union, IO, Iterable, List

from boxsdk.util.datetime_formatter import normalize_date_to_rfc3339_format
from .item import Item
from ..util.api_call_decorator import api_call
from ..util.deprecation_decorator import deprecated
Expand Down Expand Up @@ -717,3 +719,17 @@ def copy(
session=self._session,
response_object=response,
)

@api_call
def set_disposition_at(self, date_time: Union[datetime, str]) -> 'File':
"""
Modifies the retention expiration timestamp for the given file. This date can't be shortened once set on a file.
:param date_time:
A datetime str, eg. '2012-12-12T10:53:43-08:00' or datetime.datetime object. If no timezone info provided,
local timezone will be aplied.
:return:
Updated 'File' object
"""
data = {'disposition_at': normalize_date_to_rfc3339_format(date_time)}
return self.update_info(data=data)
21 changes: 21 additions & 0 deletions boxsdk/util/datetime_formatter.py
@@ -0,0 +1,21 @@
from datetime import datetime
from typing import Union

from dateutil import parser


def normalize_date_to_rfc3339_format(date: Union[datetime, str]) -> str:
"""
Normalizes any datetime string or object to rfc3339 format.
:param date: datetime str or datetime object
:return: date-time str in rfc3339 format
"""
if isinstance(date, str):
date = parser.parse(date)

if not isinstance(date, datetime):
raise TypeError(f"Got unsupported type {date.__class__.__name__!r} for date.")

timezone_aware_datetime = date if date.tzinfo is not None else date.astimezone()
return timezone_aware_datetime.isoformat(timespec='seconds')
8 changes: 8 additions & 0 deletions docs/source/boxsdk.util.rst
Expand Up @@ -20,6 +20,14 @@ boxsdk.util.chunked\_uploader module
:undoc-members:
:show-inheritance:

boxsdk.util.datetime\_formatter module
--------------------------------------

.. automodule:: boxsdk.util.datetime_formatter
:members:
:undoc-members:
:show-inheritance:

boxsdk.util.default\_arg\_value module
--------------------------------------

Expand Down
3 changes: 2 additions & 1 deletion setup.py
Expand Up @@ -55,7 +55,8 @@ def main():
'attrs>=17.3.0',
'requests>=2.4.3',
'requests-toolbelt>=0.4.0, <1.0.0',
'wrapt>=1.10.1'
'wrapt>=1.10.1',
'python-dateutil', # To be removed after dropping Python 3.6
]
redis_requires = ['redis>=2.10.3']
jwt_requires = ['pyjwt>=1.3.0', 'cryptography>=3']
Expand Down
17 changes: 17 additions & 0 deletions test/conftest.py
@@ -1,9 +1,11 @@
import datetime
import json
import logging
import sys
from unittest.mock import Mock

import pytest
import pytz

from boxsdk.network.default_network import DefaultNetworkResponse

Expand Down Expand Up @@ -268,3 +270,18 @@ def mock_enterprise_id():
@pytest.fixture(scope='module')
def mock_group_id():
return 'fake-group-99'


@pytest.fixture(scope='module')
def mock_datetime_rfc3339_str():
return '2035-03-04T10:14:24+14:00'


@pytest.fixture(scope='module')
def mock_timezone_aware_datetime_obj():
return datetime.datetime(2035, 3, 4, 10, 14, 24, microsecond=500, tzinfo=pytz.timezone('US/Alaska'))


@pytest.fixture(scope='module')
def mock_timezone_naive_datetime_obj():
return datetime.datetime(2035, 3, 4, 10, 14, 24, microsecond=500)
45 changes: 45 additions & 0 deletions test/unit/object/test_file.py
Expand Up @@ -3,6 +3,8 @@
from unittest.mock import mock_open, patch, Mock

import pytest
from pytest_lazyfixture import lazy_fixture

from boxsdk.config import API
from boxsdk.exception import BoxAPIException
from boxsdk.object.comment import Comment
Expand Down Expand Up @@ -943,3 +945,46 @@ def test_get_thumbnail_representation_not_available(
params={'fields': 'representations'},
)
assert thumb == b''


@pytest.mark.parametrize(
'disposition_at',
(
lazy_fixture('mock_datetime_rfc3339_str'),
"2035-03-04T10:14:24.000+14:00",
"2035/03/04 10:14:24.000+14:00",
lazy_fixture('mock_timezone_aware_datetime_obj'),
)
)
def test_set_diposition_at(
test_file,
mock_box_session,
disposition_at,
mock_datetime_rfc3339_str,
):
expected_url = test_file.get_url()
expected_data = {'disposition_at': mock_datetime_rfc3339_str}

test_file.set_disposition_at(disposition_at)

mock_box_session.put.assert_called_once_with(
expected_url,
data=json.dumps(expected_data),
headers=None,
params=None,
)


@pytest.mark.parametrize(
'disposition_at',
(
None,
Mock()
)
)
def test_raise_exception_when_set_diposition_at_datetime_is_invalid(
test_file,
disposition_at,
):
with pytest.raises(Exception):
test_file.set_disposition_at(disposition_at)
77 changes: 77 additions & 0 deletions test/unit/util/test_datetime_formatter.py
@@ -0,0 +1,77 @@
from unittest.mock import Mock

import datetime
import pytest

from pytest_lazyfixture import lazy_fixture

from boxsdk.util import datetime_formatter


@pytest.mark.parametrize(
"valid_datetime_format",
(
"2035-03-04T10:14:24+14:00",
"2035-03-04T10:14:24-04:00",
lazy_fixture("mock_datetime_rfc3339_str"),
),
)
def test_leave_datetime_string_unchanged_when_rfc3339_formatted_str_provided(
valid_datetime_format,
):
formatted_str = datetime_formatter.normalize_date_to_rfc3339_format(
valid_datetime_format
)
assert formatted_str == valid_datetime_format


@pytest.mark.parametrize(
"other_datetime_format",
(
"2035-03-04T10:14:24.000+14:00",
"2035-03-04 10:14:24.000+14:00",
"2035/03/04 10:14:24.000+14:00",
"2035/03/04T10:14:24+14:00",
"2035/3/4T10:14:24+14:00",
lazy_fixture('mock_timezone_aware_datetime_obj'),
),
)
def test_normalize_date_to_rfc3339_format_timezone_aware_datetime(
other_datetime_format,
mock_datetime_rfc3339_str,
):
formatted_str = datetime_formatter.normalize_date_to_rfc3339_format(
other_datetime_format
)
assert formatted_str == mock_datetime_rfc3339_str


@pytest.mark.parametrize(
"timezone_naive_datetime",
(
"2035-03-04T10:14:24.000",
"2035-03-04T10:14:24",
lazy_fixture('mock_timezone_naive_datetime_obj')
),
)
def test_add_timezone_info_when_timezone_naive_datetime_provided(
timezone_naive_datetime,
mock_timezone_naive_datetime_obj,
):
formatted_str = datetime_formatter.normalize_date_to_rfc3339_format(
timezone_naive_datetime
)

local_timezone = datetime.datetime.now().tzinfo
expected_datetime = mock_timezone_naive_datetime_obj.astimezone(
tz=local_timezone
).isoformat(timespec="seconds")
assert formatted_str == expected_datetime


@pytest.mark.parametrize("inavlid_datetime_object", (None, Mock()))
def test_throw_type_error_when_invalid_datetime_object_provided(
inavlid_datetime_object,
):
with pytest.raises(TypeError):
datetime_formatter.normalize_date_to_rfc3339_format(inavlid_datetime_object)

0 comments on commit 91b1373

Please sign in to comment.