Skip to content

Commit

Permalink
[69624] Scheduler API Support (#173)
Browse files Browse the repository at this point in the history
This PR enables SDK support for the Scheduler API.
  • Loading branch information
mrashed-dev authored Nov 26, 2021
1 parent 8ed44e7 commit f412d73
Show file tree
Hide file tree
Showing 7 changed files with 756 additions and 14 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

Unreleased (dev)
----------------
* Add support for Scheduler API
* Add support for Event notifications
* Add support for Component CRUD
* Add metadata support for `Calendar`, `Message` and `Account`
Expand Down
51 changes: 37 additions & 14 deletions nylas/client/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import print_function

import sys
from os import environ
from base64 import b64encode
Expand Down Expand Up @@ -31,6 +32,9 @@
Component,
)
from nylas.client.neural_api_models import Neural
from nylas.client.scheduler_restful_model_collection import (
SchedulerRestfulModelCollection,
)
from nylas.utils import timestamp_from_dt, create_request_body

DEBUG = environ.get("NYLAS_CLIENT_DEBUG")
Expand Down Expand Up @@ -421,6 +425,10 @@ def room_resources(self):
def calendars(self):
return RestfulModelCollection(Calendar, self)

@property
def scheduler(self):
return SchedulerRestfulModelCollection(self)

@property
def components(self):
return RestfulModelCollection(Component, self)
Expand Down Expand Up @@ -466,11 +474,13 @@ def _get_resources(self, cls, extra=None, **filters):
return [cls.create(self, **x) for x in results if x is not None]

def _get_resource_raw(
self, cls, id, extra=None, headers=None, stream=False, **filters
self, cls, id, extra=None, headers=None, stream=False, path=None, **filters
):
"""Get an individual REST resource"""
if path is None:
path = cls.collection_name
postfix = "/{}".format(extra) if extra else ""
path = "/{}".format(cls.collection_name) if cls.collection_name else ""
path = "/{}".format(path) if path else ""
id = "/{}".format(id) if id else ""
if not cls.api_root:
url = "{server}{path}{id}{postfix}".format(
Expand Down Expand Up @@ -578,16 +588,20 @@ def _delete_resource(self, cls, id, data=None, **kwargs):
else:
_validate(session.delete(url))

def _update_resource(self, cls, id, data, **kwargs):
def _put_resource(self, cls, id, data, extra=None, path=None, **kwargs):
if path is None:
path = cls.collection_name
name = "{prefix}{path}".format(
prefix="/{}/{}".format(cls.api_root, self.client_id)
if cls.api_root
else "",
path="/{}".format(cls.collection_name) if cls.collection_name else "",
path="/{}".format(path) if path else "",
)

postfix = "/{}".format(extra) if extra else ""
url = (
URLObject(self.api_server)
.with_path("{name}/{id}".format(name=name, id=id))
.with_path("{name}/{id}{postfix}".format(name=name, id=id, postfix=postfix))
.set_query_params(**kwargs)
)

Expand All @@ -596,17 +610,20 @@ def _update_resource(self, cls, id, data, **kwargs):
converted_data = create_request_body(data, cls.datetime_attrs)
response = session.put(url, json=converted_data)

result = _validate(response).json()
return cls.create(self, **result)
result = _validate(response)
return result.json()

def _call_resource_method(self, cls, id, method_name, data):
"""POST a dictionary to an API method,
for example /a/.../accounts/id/upgrade"""
def _update_resource(self, cls, id, data, **kwargs):
result = self._put_resource(cls, id, data, kwargs)
return cls.create(self, **result)

path = "/{}".format(cls.collection_name) if cls.collection_name else ""
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 ""
if not cls.api_root:
url_path = "/{name}/{id}/{method}".format(
name=cls.collection_name, id=id, method=method_name
url_path = "{name}/{id}/{method}".format(
name=path, id=id, method=method_name
)
else:
# Management method.
Expand All @@ -624,7 +641,13 @@ def _call_resource_method(self, cls, id, method_name, data):
session = self._get_http_session(cls.api_root)
response = session.post(url, json=converted_data)

result = _validate(response).json()
return _validate(response).json()

def _call_resource_method(self, cls, id, method_name, data):
"""POST a dictionary to an API method,
for example /a/.../accounts/id/upgrade"""

result = self._post_resource(cls, id, method_name, data)
return cls.create(self, **result)

def _request_neural_resource(self, cls, data, path=None, method="PUT"):
Expand Down
43 changes: 43 additions & 0 deletions nylas/client/restful_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,49 @@ def __init__(self, api):
NylasAPIObject.__init__(self, RoomResource, api)


class Scheduler(NylasAPIObject):
attrs = [
"id",
"access_tokens",
"app_client_id",
"app_organization_id",
"config",
"edit_token",
"name",
"slug",
]
date_attrs = {
"created_at": "created_at",
"modified_at": "modified_at",
}
collection_name = "manage/pages"

def __init__(self, api):
NylasAPIObject.__init__(self, Scheduler, api)

def get_available_calendars(self):
if not self.id:
raise ValueError("Cannot get calendars for a page without an ID")

response = self.api._get_resource_raw(Scheduler, self.id, extra="calendars")
response_body = response.json()
for body in response_body:
for i in range(len(body["calendars"])):
body["calendars"][i] = Calendar.create(self.api, **body["calendars"][i])

return response_body

def upload_image(self, content_type, object_name):
if not self.id:
raise ValueError("Cannot upload an image to a page without an ID")

data = {"contentType": content_type, "objectName": object_name}
response = self.api._put_resource(
Scheduler, self.id, data, extra="upload-image"
)
return response


class Component(NylasAPIObject):
attrs = [
"id",
Expand Down
59 changes: 59 additions & 0 deletions nylas/client/scheduler_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from nylas.client.restful_models import RestfulModel


class SchedulerTimeSlot(RestfulModel):
attrs = ["account_id", "calendar_id", "host_name", "emails"]
datetime_attrs = {"start": "start", "end": "end"}

def __init__(self, api):
RestfulModel.__init__(self, SchedulerTimeSlot, api)


class SchedulerBookingConfirmation(RestfulModel):
attrs = [
"id",
"account_id",
"additional_field_values",
"calendar_event_id",
"calendar_id",
"edit_hash",
"is_confirmed",
"location",
"recipient_email",
"recipient_locale",
"recipient_name",
"recipient_tz",
"title",
]
datetime_attrs = {"start_time": "start_time", "end_time": "end_time"}

def __init__(self, api):
RestfulModel.__init__(self, SchedulerBookingConfirmation, api)


class SchedulerBookingRequest(RestfulModel):
attrs = [
"additional_values",
"additional_emails",
"email",
"locale",
"name",
"page_hostname",
"replaces_booking_hash",
"timezone",
"slot",
]

def __init__(self, api):
RestfulModel.__init__(self, SchedulerBookingRequest, api)

def as_json(self):
dct = RestfulModel.as_json(self)
if "additional_values" not in dct or dct["additional_values"] is None:
dct["additional_values"] = {}
if "additional_emails" not in dct or dct["additional_emails"] is None:
dct["additional_emails"] = []
if "slot" in dct and isinstance(dct["slot"], SchedulerTimeSlot):
dct["slot"] = dct["slot"].as_json()

return dct
65 changes: 65 additions & 0 deletions nylas/client/scheduler_restful_model_collection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import copy

from nylas.client.restful_model_collection import RestfulModelCollection
from nylas.client.restful_models import Scheduler
from nylas.client.scheduler_models import (
SchedulerTimeSlot,
SchedulerBookingConfirmation,
)


class SchedulerRestfulModelCollection(RestfulModelCollection):
def __init__(self, api):
# Make a copy of the API as we need to change the base url for Scheduler calls
scheduler_api = copy.copy(api)
scheduler_api.api_server = "https://api.schedule.nylas.com"
RestfulModelCollection.__init__(self, Scheduler, scheduler_api)

def get_google_availability(self):
return self._execute_provider_availability("google")

def get_office_365_availability(self):
return self._execute_provider_availability("o365")

def get_page_slug(self, slug):
page_response = self.api._get_resource_raw(
self.model_class, slug, extra="info", path="schedule"
).json()
return Scheduler.create(self.api, **page_response)

def get_available_time_slots(self, slug):
response = self.api._get_resource_raw(
self.model_class, slug, extra="timeslots", path="schedule"
).json()
return [
SchedulerTimeSlot.create(self.api, **x) for x in response if x is not None
]

def book_time_slot(self, slug, timeslot):
response = self.api._post_resource(
self.model_class, slug, "timeslots", timeslot.as_json(), path="schedule"
)
return SchedulerBookingConfirmation.create(self.api, **response)

def cancel_booking(self, slug, edit_hash, reason):
return self.api._post_resource(
self.model_class,
slug,
"{}/cancel".format(edit_hash),
{"reason": reason},
path="schedule",
)

def confirm_booking(self, slug, edit_hash):
booking_response = self.api._post_resource(
self.model_class, slug, "{}/confirm".format(edit_hash), {}, path="schedule"
)
return SchedulerBookingConfirmation.create(self.api, **booking_response)

def _execute_provider_availability(self, provider):
return self.api._get_resource_raw(
self.model_class,
None,
extra="availability/{}".format(provider),
path="schedule",
).json()
Loading

0 comments on commit f412d73

Please sign in to comment.