Skip to content

Commit

Permalink
[76804] Event to ICS Support (#186)
Browse files Browse the repository at this point in the history
This PR enables support for generating an ICS file from an Event.
  • Loading branch information
mrashed-dev authored Jan 17, 2022
1 parent 877805e commit 45e7bda
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ nylas-python Changelog

v5.4.2
----------------
* Add support for `Event` to ICS
* Add missing `source` field in `Contact` class

v5.4.1
Expand Down
10 changes: 5 additions & 5 deletions nylas/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,18 +648,18 @@ def _post_resource(self, cls, id, method_name, data, path=None):
if path is None:
path = cls.collection_name
path = "/{}".format(path) if path else ""
id = "/{}".format(id) if id else ""
method = "/{}".format(method_name) if method_name else ""
if not cls.api_root:
url_path = "{name}/{id}/{method}".format(
name=path, id=id, method=method_name
)
url_path = "{name}{id}{method}".format(name=path, id=id, method=method)
else:
# Management method.
url_path = "/{prefix}/{client_id}{path}/{id}/{method}".format(
url_path = "/{prefix}/{client_id}{path}{id}{method}".format(
prefix=cls.api_root,
client_id=self.client_id,
path=path,
id=id,
method=method_name,
method=method,
)

url = URLObject(self.api_server).with_path(url_path)
Expand Down
45 changes: 45 additions & 0 deletions nylas/client/restful_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,51 @@ def rsvp(self, status, comment=None):
result = response.json()
return Event.create(self, **result)

def generate_ics(self, ical_uid=None, method=None, prodid=None):
"""
Generate an ICS file server-side, from an Event
Args:
ical_uid (str): Unique identifier used events across calendaring systems
method (str): Description of invitation and response methods for attendees
prodid (str): Company-specific unique product identifier
Returns:
str: String for writing directly into an ICS file
Raises:
ValueError: If the event does not have calendar_id or when set
RuntimeError: If the server returns an object without an ics string
"""
if not self.calendar_id or not self.when:
raise ValueError(
"Cannot generate an ICS file for an event without a Calendar ID or when set"
)

payload = {}
ics_options = {}
if self.id:
payload["event_id"] = self.id
else:
payload = self.as_json()

if ical_uid:
ics_options["ical_uid"] = ical_uid
if method:
ics_options["method"] = method
if prodid:
ics_options["prodid"] = prodid

if ics_options:
payload["ics_options"] = ics_options

response = self.api._post_resource(Event, None, "to-ics", payload)
if "ics" in response:
return response["ics"]
raise RuntimeError(
"Unexpected response from the API server. Returned 200 but no 'ics' string found."
)

def save(self, **kwargs):
if (
self.conferencing
Expand Down
7 changes: 7 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,13 @@ def callback(_request):
)


@pytest.fixture
def mock_event_generate_ics(mocked_responses, api_url, message_body):
mocked_responses.add(
responses.POST, api_url + "/events/to-ics", body=json.dumps({"ics": ""})
)


@pytest.fixture
def mock_scheduler_create_response(mocked_responses, api_url, message_body):
def callback(_request):
Expand Down
75 changes: 75 additions & 0 deletions tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,3 +545,78 @@ def test_event_notifications(mocked_responses, api_client):
assert event.notifications[0]["minutes_before_event"] == 60
assert event.notifications[0]["subject"] == "Test Event Notification"
assert event.notifications[0]["body"] == "Reminding you about our meeting."


@pytest.mark.usefixtures("mock_event_create_response", "mock_event_generate_ics")
def test_generate_ics_existing_event(mocked_responses, api_client):
event = blank_event(api_client)
event.save()
ics = event.generate_ics()
ics_request = mocked_responses.calls[1].request
assert len(mocked_responses.calls) == 2
assert event.id == "cv4ei7syx10uvsxbs21ccsezf"
assert ics_request.path_url == "/events/to-ics"
assert ics_request.method == "POST"
assert json.loads(ics_request.body) == {"event_id": "cv4ei7syx10uvsxbs21ccsezf"}


@pytest.mark.usefixtures("mock_event_create_response", "mock_event_generate_ics")
def test_generate_ics_no_event_id(mocked_responses, api_client):
event = blank_event(api_client)
ics = event.generate_ics()
ics_request = mocked_responses.calls[0].request
assert len(mocked_responses.calls) == 1
assert event.id is None
assert ics_request.path_url == "/events/to-ics"
assert ics_request.method == "POST"
assert json.loads(ics_request.body) == {
"calendar_id": "calendar_id",
"title": "Paris-Brest",
"when": {"end_time": 1409594400, "start_time": 1409594400},
}


@pytest.mark.usefixtures("mock_event_create_response", "mock_event_generate_ics")
def test_generate_ics_options(mocked_responses, api_client):
event = blank_event(api_client)
event.save()
ics = event.generate_ics(
ical_uid="test_uuid", method="request", prodid="test_prodid"
)
ics_request = mocked_responses.calls[1].request
assert len(mocked_responses.calls) == 2
assert event.id == "cv4ei7syx10uvsxbs21ccsezf"
assert ics_request.path_url == "/events/to-ics"
assert ics_request.method == "POST"
assert json.loads(ics_request.body) == {
"event_id": "cv4ei7syx10uvsxbs21ccsezf",
"ics_options": {
"ical_uid": "test_uuid",
"method": "request",
"prodid": "test_prodid",
},
}


@pytest.mark.usefixtures("mock_event_create_response", "mock_event_generate_ics")
def test_generate_ics_no_calendar_id_throws(mocked_responses, api_client):
event = blank_event(api_client)
del event.calendar_id
with pytest.raises(ValueError) as exc:
event.generate_ics()

assert str(exc.value) == (
"Cannot generate an ICS file for an event without a Calendar ID or when set"
)


@pytest.mark.usefixtures("mock_event_create_response", "mock_event_generate_ics")
def test_generate_ics_no_when_throws(mocked_responses, api_client):
event = blank_event(api_client)
del event.when
with pytest.raises(ValueError) as exc:
event.generate_ics()

assert str(exc.value) == (
"Cannot generate an ICS file for an event without a Calendar ID or when set"
)

0 comments on commit 45e7bda

Please sign in to comment.