Skip to content

Commit 8dce1d2

Browse files
author
David Baumgold
authored
Merge pull request #146 from nylas/calendar-availability
implement calendar availability
2 parents d46c6f7 + 4582031 commit 8dce1d2

4 files changed

Lines changed: 185 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ nylas-python Changelog
33

44
Unreleased (dev)
55
----------------
6-
nothing yet
6+
* Add calendar availability information, available at `/calendars/availability` API endpoint
77

88
v4.11.0
99
-------

nylas/client/client.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from os import environ
44
from base64 import b64encode
55
import json
6-
from datetime import datetime
6+
from datetime import datetime, timedelta
77

88
import requests
99
from urlobject import URLObject
@@ -243,6 +243,40 @@ def free_busy(self, emails, start_at, end_at):
243243
_validate(resp)
244244
return resp.json()
245245

246+
def availability(
247+
self, emails, duration, interval, start_at, end_at, free_busy=None
248+
):
249+
if isinstance(emails, six.string_types):
250+
emails = [emails]
251+
if isinstance(duration, timedelta):
252+
duration_minutes = duration.total_seconds() // 60
253+
else:
254+
duration_minutes = int(duration)
255+
if isinstance(interval, timedelta):
256+
interval_minutes = interval.total_seconds() // 60
257+
else:
258+
interval_minutes = int(interval)
259+
if isinstance(start_at, datetime):
260+
start_time = timestamp_from_dt(start_at)
261+
else:
262+
start_time = start_at
263+
if isinstance(end_at, datetime):
264+
end_time = timestamp_from_dt(end_at)
265+
else:
266+
end_time = end_at
267+
url = "{api_server}/calendars/availability".format(api_server=self.api_server)
268+
data = {
269+
"emails": emails,
270+
"duration_minutes": duration_minutes,
271+
"interval_minutes": interval_minutes,
272+
"start_time": start_time,
273+
"end_time": end_time,
274+
"free_busy": free_busy or [],
275+
}
276+
resp = self.session.post(url, json=data)
277+
_validate(resp)
278+
return resp.json()
279+
246280
@property
247281
def account(self):
248282
return self._get_resource(SingletonAccount, "")

tests/conftest.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,3 +1366,37 @@ def free_busy_callback(request):
13661366
content_type="application/json",
13671367
callback=free_busy_callback,
13681368
)
1369+
1370+
1371+
@pytest.fixture
1372+
def mock_availability(mocked_responses, api_url):
1373+
availability_url = "{base}/calendars/availability".format(base=api_url)
1374+
1375+
def availability_callback(request):
1376+
payload = json.loads(request.body)
1377+
resp_data = {
1378+
"object": "availability",
1379+
"time_slots": [
1380+
{
1381+
"object": "time_slot",
1382+
"status": "free",
1383+
"start_time": 1409594400,
1384+
"end_time": 1409598000,
1385+
},
1386+
{
1387+
"object": "time_slot",
1388+
"status": "free",
1389+
"start_time": 1409598000,
1390+
"end_time": 1409599000,
1391+
},
1392+
],
1393+
}
1394+
1395+
return 200, {}, json.dumps(resp_data)
1396+
1397+
mocked_responses.add_callback(
1398+
responses.POST,
1399+
availability_url,
1400+
content_type="application/json",
1401+
callback=availability_callback,
1402+
)

tests/test_events.py

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from datetime import datetime
2+
from datetime import datetime, timedelta
33
import pytest
44
from urlobject import URLObject
55
from requests import RequestException
@@ -150,3 +150,117 @@ def test_free_busy_single_email(mocked_responses, api_client):
150150
assert data["emails"] == [email]
151151
assert data["start_time"] == 946684800
152152
assert data["end_time"] == 951868800
153+
154+
155+
@pytest.mark.usefixtures("mock_availability")
156+
def test_availability_datetime(mocked_responses, api_client):
157+
emails = ["one@example.com", "two@example.com", "three@example.com"]
158+
duration = timedelta(minutes=30)
159+
interval = timedelta(hours=1, minutes=30)
160+
start_at = datetime(2020, 1, 1)
161+
end_at = datetime(2020, 1, 2)
162+
availability = api_client.availability(emails, duration, interval, start_at, end_at)
163+
164+
assert isinstance(availability, dict)
165+
assert "time_slots" in availability
166+
167+
request = mocked_responses.calls[-1].request
168+
assert URLObject(request.url).path == "/calendars/availability"
169+
data = json.loads(request.body)
170+
assert data["emails"] == emails
171+
assert data["duration_minutes"] == 30
172+
assert data["interval_minutes"] == 90
173+
assert data["start_time"] == 1577836800
174+
assert data["end_time"] == 1577923200
175+
assert data["free_busy"] == []
176+
177+
178+
@pytest.mark.usefixtures("mock_availability")
179+
def test_availability_timestamp(mocked_responses, api_client):
180+
emails = ["one@example.com", "two@example.com", "three@example.com"]
181+
duration = 30
182+
interval = 60
183+
start_time = 1580511600
184+
end_time = 1580598000
185+
availability = api_client.availability(
186+
emails, duration, interval, start_time, end_time
187+
)
188+
189+
assert isinstance(availability, dict)
190+
assert "time_slots" in availability
191+
192+
request = mocked_responses.calls[-1].request
193+
assert URLObject(request.url).path == "/calendars/availability"
194+
data = json.loads(request.body)
195+
assert data["emails"] == emails
196+
assert data["duration_minutes"] == 30
197+
assert data["interval_minutes"] == 60
198+
assert data["start_time"] == 1580511600
199+
assert data["end_time"] == 1580598000
200+
assert data["free_busy"] == []
201+
202+
203+
@pytest.mark.usefixtures("mock_availability")
204+
def test_availability_single_email(mocked_responses, api_client):
205+
email = "ben@bitdiddle.com"
206+
duration = timedelta(minutes=60)
207+
interval = 5
208+
start_at = datetime(2000, 1, 1)
209+
end_at = datetime(2000, 3, 1)
210+
availability = api_client.availability(email, duration, interval, start_at, end_at)
211+
212+
assert isinstance(availability, dict)
213+
assert "time_slots" in availability
214+
215+
request = mocked_responses.calls[-1].request
216+
assert URLObject(request.url).path == "/calendars/availability"
217+
data = json.loads(request.body)
218+
assert data["emails"] == [email]
219+
assert data["duration_minutes"] == 60
220+
assert data["interval_minutes"] == 5
221+
assert data["start_time"] == 946684800
222+
assert data["end_time"] == 951868800
223+
assert data["free_busy"] == []
224+
225+
226+
@pytest.mark.usefixtures("mock_availability")
227+
def test_availability_with_free_busy(mocked_responses, api_client):
228+
emails = [
229+
"one@example.com",
230+
"two@example.com",
231+
"three@example.com",
232+
"visitor@external.net",
233+
]
234+
duration = 48
235+
interval = timedelta(minutes=18)
236+
start_at = datetime(2020, 1, 1)
237+
end_at = datetime(2020, 1, 2)
238+
free_busy = [
239+
{
240+
"email": "visitor@external.net",
241+
"time_slots": [
242+
{
243+
"object": "time_slot",
244+
"status": "busy",
245+
"start_time": 1584377898,
246+
"end_time": 1584379800,
247+
}
248+
],
249+
}
250+
]
251+
availability = api_client.availability(
252+
emails, duration, interval, start_at, end_at, free_busy=free_busy
253+
)
254+
255+
assert isinstance(availability, dict)
256+
assert "time_slots" in availability
257+
258+
request = mocked_responses.calls[-1].request
259+
assert URLObject(request.url).path == "/calendars/availability"
260+
data = json.loads(request.body)
261+
assert data["emails"] == emails
262+
assert data["duration_minutes"] == 48
263+
assert data["interval_minutes"] == 18
264+
assert data["start_time"] == 1577836800
265+
assert data["end_time"] == 1577923200
266+
assert data["free_busy"] == free_busy

0 commit comments

Comments
 (0)