From 2138271fd6a207db07f99440c6424a8d4ac75e72 Mon Sep 17 00:00:00 2001 From: Mostafa Rashed <17770919+mrashed-dev@users.noreply.github.com> Date: Tue, 12 Jul 2022 16:15:59 -0400 Subject: [PATCH 1/5] Add new calendars field in availability functions --- nylas/client/client.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/nylas/client/client.py b/nylas/client/client.py index b5b92f6b..5b68605d 100644 --- a/nylas/client/client.py +++ b/nylas/client/client.py @@ -270,7 +270,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 +287,9 @@ 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 + resp = self._request(HttpMethod.POST, url, json=data, cls=Calendar) _validate(resp) return resp.json() @@ -321,6 +324,7 @@ def availability( round_robin=None, free_busy=None, open_hours=None, + calendars=None, ): if isinstance(emails, six.string_types): emails = [emails] @@ -359,6 +363,8 @@ 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 resp = self._request(HttpMethod.POST, url, json=data, cls=Calendar) _validate(resp) @@ -374,6 +380,7 @@ def consecutive_availability( buffer=None, free_busy=None, open_hours=None, + calendars=None, ): if isinstance(emails, six.string_types): emails = [[emails]] @@ -412,6 +419,8 @@ def consecutive_availability( } if buffer is not None: data["buffer"] = buffer + if calendars is not None and len(calendars) > 0: + data["calendars"] = calendars resp = self._request(HttpMethod.POST, url, json=data, cls=Calendar) _validate(resp) From b4fe96f1047075c3147f44cffb0584f2751b8791 Mon Sep 17 00:00:00 2001 From: Mostafa Rashed <17770919+mrashed-dev@users.noreply.github.com> Date: Tue, 12 Jul 2022 16:16:23 -0400 Subject: [PATCH 2/5] Add validation for availability queries --- nylas/client/client.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nylas/client/client.py b/nylas/client/client.py index 5b68605d..020d9fcd 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.""" @@ -290,6 +297,7 @@ def free_busy(self, emails, start_at, end_at, calendars=None): 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() @@ -366,6 +374,7 @@ def availability( 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() @@ -422,6 +431,7 @@ def consecutive_availability( 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() From de8fcb676807b320d57c6787a08f2d9005d56321 Mon Sep 17 00:00:00 2001 From: Mostafa Rashed <17770919+mrashed-dev@users.noreply.github.com> Date: Tue, 12 Jul 2022 16:17:01 -0400 Subject: [PATCH 3/5] Check if emails array is populated first --- nylas/client/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nylas/client/client.py b/nylas/client/client.py index 020d9fcd..9bc2f458 100644 --- a/nylas/client/client.py +++ b/nylas/client/client.py @@ -393,7 +393,7 @@ def consecutive_availability( ): 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) From ba55a428ed227d1678f8d0a000a4ea3a0effd317 Mon Sep 17 00:00:00 2001 From: Mostafa Rashed <17770919+mrashed-dev@users.noreply.github.com> Date: Tue, 12 Jul 2022 16:17:12 -0400 Subject: [PATCH 4/5] Add testing for new calendars field --- tests/test_events.py | 130 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) 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 From c1c199e267000a984f5090a8ad7ee966d7bf90ff Mon Sep 17 00:00:00 2001 From: Mostafa Rashed <17770919+mrashed-dev@users.noreply.github.com> Date: Tue, 12 Jul 2022 16:17:57 -0400 Subject: [PATCH 5/5] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) 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