diff --git a/CHANGELOG.md b/CHANGELOG.md index f85b84d6..b11715c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ nylas-python Changelog ====================== +Unreleased +---------------- +* Add support for `calendar` field in free-busy, availability, and consecutive availability queries + v5.9.2 ---------------- * Add `enforce_read_only` parameter to overriding `as_json` functions diff --git a/nylas/client/client.py b/nylas/client/client.py index b5b92f6b..9bc2f458 100644 --- a/nylas/client/client.py +++ b/nylas/client/client.py @@ -72,6 +72,13 @@ def _validate(response): return response +def _validate_availability_query(query): + if (query.get("emails", None) is None or len(query["emails"]) == 0) and ( + query.get("calendars", None) is None or len(query["calendars"]) == 0 + ): + raise ValueError("Must set either 'emails' or 'calendars' in the query.") + + class APIClient(json.JSONEncoder): """API client for the Nylas API.""" @@ -270,7 +277,7 @@ def token_info(self): _validate(resp).json() return resp.json() - def free_busy(self, emails, start_at, end_at): + def free_busy(self, emails, start_at, end_at, calendars=None): if isinstance(emails, six.string_types): emails = [emails] if isinstance(start_at, datetime): @@ -287,6 +294,10 @@ def free_busy(self, emails, start_at, end_at): "start_time": start_time, "end_time": end_time, } + if calendars is not None and len(calendars) > 0: + data["calendars"] = calendars + + _validate_availability_query(data) resp = self._request(HttpMethod.POST, url, json=data, cls=Calendar) _validate(resp) return resp.json() @@ -321,6 +332,7 @@ def availability( round_robin=None, free_busy=None, open_hours=None, + calendars=None, ): if isinstance(emails, six.string_types): emails = [emails] @@ -359,7 +371,10 @@ def availability( data["round_robin"] = round_robin if event_collection_id is not None: data["event_collection_id"] = event_collection_id + if calendars is not None and len(calendars) > 0: + data["calendars"] = calendars + _validate_availability_query(data) resp = self._request(HttpMethod.POST, url, json=data, cls=Calendar) _validate(resp) return resp.json() @@ -374,10 +389,11 @@ def consecutive_availability( buffer=None, free_busy=None, open_hours=None, + calendars=None, ): if isinstance(emails, six.string_types): emails = [[emails]] - elif isinstance(emails[0], list) is False: + elif len(emails) > 0 and isinstance(emails[0], list) is False: raise ValueError("'emails' must be a list of lists.") if isinstance(duration, timedelta): duration_minutes = int(duration.total_seconds() // 60) @@ -412,7 +428,10 @@ def consecutive_availability( } if buffer is not None: data["buffer"] = buffer + if calendars is not None and len(calendars) > 0: + data["calendars"] = calendars + _validate_availability_query(data) resp = self._request(HttpMethod.POST, url, json=data, cls=Calendar) _validate(resp) return resp.json() diff --git a/tests/test_events.py b/tests/test_events.py index 10cf2afc..3de943ec 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -292,6 +292,41 @@ def test_free_busy_single_email(mocked_responses, api_client): assert data["end_time"] == 951868800 +@pytest.mark.usefixtures("mock_free_busy") +def test_free_busy_with_calendars(mocked_responses, api_client): + email = "ben@bitdiddle.com" + start_at = datetime(2000, 1, 1) + end_at = datetime(2000, 3, 1) + calendars = [ + { + "account_id": "test_account_id", + "calendar_ids": ["example_calendar_a", "example_calendar_b"], + } + ] + api_client.free_busy([email], start_at, end_at, calendars) + + request = mocked_responses.calls[-1].request + assert URLObject(request.url).path == "/calendars/free-busy" + data = json.loads(request.body) + assert data["emails"] == [email] + assert data["start_time"] == 946684800 + assert data["end_time"] == 951868800 + assert len(data["calendars"]) == 1 + assert data["calendars"][0] == { + "account_id": "test_account_id", + "calendar_ids": ["example_calendar_a", "example_calendar_b"], + } + + +@pytest.mark.usefixtures("mock_free_busy") +def test_free_busy_without_emails_or_calendar(mocked_responses, api_client): + start_at = datetime(2000, 1, 1) + end_at = datetime(2000, 3, 1) + with pytest.raises(ValueError) as excinfo: + api_client.free_busy([], start_at, end_at) + assert "Must set either 'emails' or 'calendars' in the query." in str(excinfo) + + @pytest.mark.usefixtures("mock_availability") def test_availability_datetime(mocked_responses, api_client): emails = ["one@example.com", "two@example.com", "three@example.com"] @@ -415,6 +450,56 @@ def test_availability_with_free_busy(mocked_responses, api_client): assert data["free_busy"] == free_busy +@pytest.mark.usefixtures("mock_availability") +def test_availability_with_calendars(mocked_responses, api_client): + emails = [ + "one@example.com", + "two@example.com", + "three@example.com", + "visitor@external.net", + ] + duration = 48 + interval = timedelta(minutes=18) + start_at = datetime(2020, 1, 1) + end_at = datetime(2020, 1, 2) + calendars = [ + { + "account_id": "test_account_id", + "calendar_ids": ["example_calendar_a", "example_calendar_b"], + } + ] + api_client.availability( + emails, duration, interval, start_at, end_at, calendars=calendars + ) + + request = mocked_responses.calls[-1].request + assert URLObject(request.url).path == "/calendars/availability" + data = json.loads(request.body) + assert data["emails"] == emails + assert data["duration_minutes"] == 48 + assert isinstance(data["duration_minutes"], int) + assert data["interval_minutes"] == 18 + assert isinstance(data["interval_minutes"], int) + assert data["start_time"] == 1577836800 + assert data["end_time"] == 1577923200 + assert len(data["calendars"]) == 1 + assert data["calendars"][0] == { + "account_id": "test_account_id", + "calendar_ids": ["example_calendar_a", "example_calendar_b"], + } + + +@pytest.mark.usefixtures("mock_availability") +def test_availability_without_emails_or_calendar(mocked_responses, api_client): + duration = 48 + interval = timedelta(minutes=18) + start_at = datetime(2000, 1, 1) + end_at = datetime(2000, 3, 1) + with pytest.raises(ValueError) as excinfo: + api_client.availability([], duration, interval, start_at, end_at) + assert "Must set either 'emails' or 'calendars' in the query." in str(excinfo) + + @pytest.mark.usefixtures("mock_availability") def test_consecutive_availability(mocked_responses, api_client): emails = [["one@example.com"], ["two@example.com", "three@example.com"]] @@ -521,6 +606,51 @@ def test_consecutive_availability_free_busy(mocked_responses, api_client): assert data["open_hours"][0]["end"] == "14:00" +@pytest.mark.usefixtures("mock_availability") +def test_consecutive_availability_with_calendars(mocked_responses, api_client): + emails = [["one@example.com"], ["two@example.com", "three@example.com"]] + duration = timedelta(minutes=30) + interval = timedelta(hours=1, minutes=30) + start_at = datetime(2020, 1, 1) + end_at = datetime(2020, 1, 2) + calendars = [ + { + "account_id": "test_account_id", + "calendar_ids": ["example_calendar_a", "example_calendar_b"], + } + ] + api_client.consecutive_availability( + emails, duration, interval, start_at, end_at, calendars=calendars + ) + + request = mocked_responses.calls[-1].request + assert URLObject(request.url).path == "/calendars/availability/consecutive" + data = json.loads(request.body) + assert data["emails"] == emails + assert data["duration_minutes"] == 30 + assert isinstance(data["duration_minutes"], int) + assert data["interval_minutes"] == 90 + assert isinstance(data["interval_minutes"], int) + assert data["start_time"] == 1577836800 + assert data["end_time"] == 1577923200 + assert len(data["calendars"]) == 1 + assert data["calendars"][0] == { + "account_id": "test_account_id", + "calendar_ids": ["example_calendar_a", "example_calendar_b"], + } + + +@pytest.mark.usefixtures("mock_availability") +def test_availability_without_emails_or_calendar(mocked_responses, api_client): + duration = 48 + interval = timedelta(minutes=18) + start_at = datetime(2000, 1, 1) + end_at = datetime(2000, 3, 1) + with pytest.raises(ValueError) as excinfo: + api_client.consecutive_availability([], duration, interval, start_at, end_at) + assert "Must set either 'emails' or 'calendars' in the query." in str(excinfo) + + @pytest.mark.usefixtures("mock_availability") def test_consecutive_availability_invalid_open_hours_email( mocked_responses, api_client