diff --git a/CHANGELOG.md b/CHANGELOG.md index 51f7de96..391f1a60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/nylas/client/client.py b/nylas/client/client.py index 2c1220a8..37a745e2 100644 --- a/nylas/client/client.py +++ b/nylas/client/client.py @@ -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) diff --git a/nylas/client/restful_models.py b/nylas/client/restful_models.py index 0fcde8d7..99a05464 100644 --- a/nylas/client/restful_models.py +++ b/nylas/client/restful_models.py @@ -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 diff --git a/tests/conftest.py b/tests/conftest.py index 2107b98a..25b0bba8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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): diff --git a/tests/test_events.py b/tests/test_events.py index 10955430..248cc81e 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -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" + )