Description
I am trying to create a meeting with OAuth with python 3.7. The below written code works perfectly for getting the meetings lists. Since the recent update, this code does not work anymore. However I am puzzled how to adopt the code and the documentation did not help very much.
Error
{‘code’: 300, ‘message’: ‘Request Body should be a valid JSON object.’}
Which App Type (OAuth / Chatbot / JWT / Webhook)?
OAuth
Which Endpoint/s?
API endpoint: https://api.zoom.us/v2
How To Reproduce (If applicable)
The code is used is divided into:
- Utility classes and functions
- Zoom.us REST API Python Client
- API Call to create meeting
This is the code:
#1. Utility classes and functions
“”“Utility classes and functions”""
from future import absolute_import, unicode_literals
from urllib.parse import quote
import contextlib
import json
import requests
import time
import jwt
API_VERSION_2 = 2
class ApiClient(object):
“”“Simple wrapper for REST API requests”""
def __init__(self, base_uri=None, timeout=15, **kwargs):
"""Setup a new API Client
:param base_uri: The base URI to the API
:param timeout: The timeout to use for requests
:param kwargs: Any other attributes. These will be added as
attributes to the ApiClient object.
"""
self.base_uri = base_uri
self.timeout = timeout
for k, v in kwargs.items():
setattr(self, k, v)
@property
def timeout(self):
"""The timeout"""
return self._timeout
@timeout.setter
def timeout(self, value):
"""The default timeout"""
if value is not None:
try:
value = int(value)
except ValueError:
raise ValueError("timeout value must be an integer")
self._timeout = value
@property
def base_uri(self):
"""The base_uri"""
return self._base_uri
@base_uri.setter
def base_uri(self, value):
"""The default base_uri"""
if value and value.endswith("/"):
value = value[:-1]
self._base_uri = value
def url_for(self, endpoint):
"""Get the URL for the given endpoint
:param endpoint: The endpoint
:return: The full URL for the endpoint
"""
if not endpoint.startswith("/"):
endpoint = "/{}".format(endpoint)
if endpoint.endswith("/"):
endpoint = endpoint[:-1]
return self.base_uri + endpoint
def get_request(self, endpoint, params=None, headers=None):
"""Helper function for GET requests
:param endpoint: The endpoint
:param params: The URL parameters
:param headers: request headers
:return: The :class:``requests.Response`` object for this request
"""
if headers is None and self.config.get("version") == API_VERSION_2:
headers = {"Authorization": "Bearer {}".format(self.config.get("token"))}
return requests.get(
self.url_for(endpoint), params=params, headers=headers, timeout=self.timeout
)
def post_request(
self, endpoint, params=None, data=None, headers=None, cookies=None
):
"""Helper function for POST requests
:param endpoint: The endpoint
:param params: The URL parameters
:param data: The data (either as a dict or dumped JSON string) to
include with the POST
:param headers: request headers
:param cookies: request cookies
:return: The :class:``requests.Response`` object for this request
"""
if data and not is_str_type(data):
data = json.dumps(data)
if headers is None and self.config.get("version") == API_VERSION_2:
headers = {
"Authorization": "Bearer {}".format(self.config.get("token")),
"Content-Type": "application/json",
}
return requests.post(
self.url_for(endpoint),
params=params,
data=data,
headers=headers,
cookies=cookies,
timeout=self.timeout,
)
def patch_request(
self, endpoint, params=None, data=None, headers=None, cookies=None
):
"""Helper function for PATCH requests
:param endpoint: The endpoint
:param params: The URL parameters
:param data: The data (either as a dict or dumped JSON string) to
include with the PATCH
:param headers: request headers
:param cookies: request cookies
:return: The :class:``requests.Response`` object for this request
"""
if data and not is_str_type(data):
data = json.dumps(data)
if headers is None and self.config.get("version") == API_VERSION_2:
headers = {
"Authorization": "Bearer {}".format(self.config.get("token")),
"Content-Type": "application/json",
}
return requests.patch(
self.url_for(endpoint),
params=params,
data=data,
headers=headers,
cookies=cookies,
timeout=self.timeout,
)
def delete_request(
self, endpoint, params=None, data=None, headers=None, cookies=None
):
"""Helper function for DELETE requests
:param endpoint: The endpoint
:param params: The URL parameters
:param data: The data (either as a dict or dumped JSON string) to
include with the DELETE
:param headers: request headers
:param cookies: request cookies
:return: The :class:``requests.Response`` object for this request
"""
if data and not is_str_type(data):
data = json.dumps(data)
if headers is None and self.config.get("version") == API_VERSION_2:
headers = {"Authorization": "Bearer {}".format(self.config.get("token"))}
return requests.delete(
self.url_for(endpoint),
params=params,
data=data,
headers=headers,
cookies=cookies,
timeout=self.timeout,
)
def put_request(self, endpoint, params=None, data=None, headers=None, cookies=None):
"""Helper function for PUT requests
:param endpoint: The endpoint
:param params: The URL paramaters
:param data: The data (either as a dict or dumped JSON string) to
include with the PUT
:param headers: request headers
:param cookies: request cookies
:return: The :class:``requests.Response`` object for this request
"""
if data and not is_str_type(data):
data = json.dumps(data)
if headers is None and self.config.get("version") == API_VERSION_2:
headers = {"Authorization": "Bearer {}".format(self.config.get("token"))}
return requests.put(
self.url_for(endpoint),
params=params,
data=data,
headers=headers,
cookies=cookies,
timeout=self.timeout,
)
@contextlib.contextmanager
def ignored(*exceptions):
“”“Simple context manager to ignore expected Exceptions
:param *exceptions: The exceptions to safely ignore
“””
try:
yield
except exceptions:
pass
def is_str_type(val):
“”“Check whether the input is of a string type.
We use this method to ensure python 2-3 capatibility.
:param val: The value to check wither it is a string
:return: In python2 it will return True
if :attr:val
is either an
instance of str or unicode. In python3 it will return True
if
it is an instance of str
“””
with ignored(NameError):
return isinstance(val, basestring)
return isinstance(val, str)
def require_keys(d, keys, allow_none=True):
“”“Require that the object have the given keys
:param d: The dict the check
:param keys: The keys to check :attr:obj
for. This can either be a single
string, or an iterable of strings
:param allow_none: Whether None
values are allowed
:raises:
:ValueError: If any of the keys are missing from the obj
“””
if is_str_type(keys):
keys = [keys]
for k in keys:
if k not in d:
raise ValueError("’{}’ must be set".format(k))
if not allow_none and d[k] is None:
raise ValueError("’{}’ cannot be None".format(k))
return True
def date_to_str(d):
“”“Convert date and datetime objects to a string
Note, this does not do any timezone conversion.
:param d: The :class:datetime.date
or :class:datetime.datetime
to
convert to a string
:returns: The string representation of the date
“””
return d.strftime("%Y-%m-%dT%H:%M:%SZ")
def generate_jwt(key, secret):
header = {“alg”: “HS256”, “typ”: “JWT”}
payload = {"iss": key, "exp": int(time.time() + 3600)}
token = jwt.encode(payload, secret, algorithm="HS256", headers=header)
return token.decode("utf-8")
def encode_uuid(val):
“”“Encode UUID as described by ZOOM API documentation
> Note: Please double encode your UUID when using this API if the UUID
> begins with a '/'or contains ‘//’ in it.
:param val: The UUID to encode
:returns: The encoded UUID
“””
if val[0] == “/” or “//” in val:
val = quote(quote(val, safe=""), safe="")
return val
print(“done”)
#2. Zoom.us REST API Python Client
“”“Zoom.us REST API Python Client”""
from future import absolute_import, unicode_literals
from zoomus import components, util
from zoomus.util import API_VERSION_2
API_BASE_URIS = {
API_VERSION_2: “https://api.zoom.us/v2”,
}
COMPONENT_CLASSES = {
API_VERSION_2: {
“user”: components.user.UserComponentV2,
“meeting”: components.meeting.MeetingComponentV2,
#“metric”: components.metric.MetricComponentV2,
#“past_meeting”: components.past_meeting.PastMeetingComponentV2,
“report”: components.report.ReportComponentV2,
“webinar”: components.webinar.WebinarComponentV2,
“recording”: components.recording.RecordingComponentV2,
#“phone”: components.phone.PhoneComponentV2,
},
}
class ZoomClient(util.ApiClient):
“”“Zoom.us REST API Python Client”""
"""Base URL for Zoom API"""
def __init__(
self, api_key, api_secret, data_type="json", timeout=15, version=API_VERSION_2
):
"""Create a new Zoom client
:param api_key: The Zooom.us API key
:param api_secret: The Zoom.us API secret
:param data_type: The expected return data type. Either 'json' or 'xml'
:param timeout: The time out to use for API requests
"""
try:
BASE_URI = API_BASE_URIS[version]
self.components = COMPONENT_CLASSES[version].copy()
except KeyError:
raise RuntimeError("API version not supported: %s" % version)
super(ZoomClient, self).__init__(base_uri=BASE_URI, timeout=timeout)
# Setup the config details
self.config = {
"api_key": api_key,
"api_secret": api_secret,
"data_type": data_type,
"version": version,
"token": util.generate_jwt(api_key, api_secret),
}
# Instantiate the components
for key in self.components.keys():
self.components[key] = self.components[key](
base_uri=BASE_URI, config=self.config
)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return
def refresh_token(self):
self.config["token"] = (
util.generate_jwt(self.config["api_key"], self.config["api_secret"]),
)
@property
def api_key(self):
"""The Zoom.us api_key"""
return self.config.get("api_key")
@api_key.setter
def api_key(self, value):
"""Set the api_key"""
self.config["api_key"] = value
self.refresh_token()
@property
def api_secret(self):
"""The Zoom.us api_secret"""
return self.config.get("api_secret")
@api_secret.setter
def api_secret(self, value):
"""Set the api_secret"""
self.config["api_secret"] = value
self.refresh_token()
@property
def meeting(self):
"""Get the meeting component"""
return self.components.get("meeting")
@property
def metric(self):
"""Get the metric component"""
return self.components.get("metric")
@property
def report(self):
"""Get the report component"""
return self.components.get("report")
@property
def user(self):
"""Get the user component"""
return self.components.get("user")
@property
def webinar(self):
"""Get the webinar component"""
return self.components.get("webinar")
@property
def recording(self):
"""Get the recording component"""
return self.components.get("recording")
@property
def phone(self):
"""Get the phone component"""
return self.components.get("phone")
print(“done”)
#3. API Call to create meeting
import datetime
import json
client = ZoomClient(‘api_key’, ‘api_sec’)
user_list_response = client.user.list()
user_list = json.loads(user_list_response.content)
datetime_object = datetime.datetime.strptime(“2020-03-21 20:00:00+00:00”[:19], ‘%Y-%m-%d %H:%M:%S’)
datetime_object = datetime_object.strftime(’%Y-%m-%dT%H:%M:%SZ’)
datetime_object = datetime.datetime.strptime(datetime_object, ‘%Y-%m-%dT%H:%M:%SZ’)
for user in user_list[‘users’]:
user_id = user[‘id’]
#print(json.loads(client.meeting.list(user_id=user_id).content)) #THIS WORKS
print(json_file = json.loads(client.meeting.create(user_id=user_id, host_id=“host_id”, duration=60, timezone=“Europe/Vienna”, topic=“Sophie & Chris”, type=“2”, start_time=datetime_object).content))
Additional context
Can anybody please help with the current code or provide a code snippet for this problem? Thank you!