Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
23 changes: 21 additions & 2 deletions nylas/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down Expand Up @@ -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):
Expand All @@ -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()
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
130 changes: 130 additions & 0 deletions tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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"]]
Expand Down Expand Up @@ -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
Expand Down