From ce958a692596348739e37b7b82addd511229d0bc Mon Sep 17 00:00:00 2001 From: SushilMallRC Date: Tue, 2 Apr 2024 13:28:22 +0530 Subject: [PATCH] Add inline comments and docstring --- ringcentral/core/__init__.py | 47 ++++++++ .../deprecated_pubnub_subscription/events.py | 13 ++ .../subscription.py | 65 ++++++++++ ringcentral/http/client.py | 33 +++-- ringcentral/platform/events.py | 11 ++ ringcentral/platform/platform.py | 113 +++++++++++++++++- 6 files changed, 273 insertions(+), 9 deletions(-) diff --git a/ringcentral/core/__init__.py b/ringcentral/core/__init__.py index 4ef75fd..031cfae 100644 --- a/ringcentral/core/__init__.py +++ b/ringcentral/core/__init__.py @@ -9,6 +9,22 @@ def is_third(): def urlencode(s): + """ + Encodes the given dictionary `s` into a URL-encoded string. + + Parameters: + s (dict): A dictionary containing the key-value pairs to be encoded. + + Returns: + str: A URL-encoded string representing the input dictionary. + + Raises: + Exception: If neither `urllib.urlencode` nor `urllib.parse.urlencode` is available. + + Note: + This function checks for the presence of `urllib.urlencode` and `urllib.parse.urlencode` + to ensure compatibility across Python 2 and Python 3. + """ if hasattr(urllib, 'urlencode'): return urllib.urlencode(s) elif hasattr(urllib, 'parse'): @@ -18,10 +34,32 @@ def urlencode(s): def iterator(thing): + """ + Returns an iterator over key-value pairs of `thing`. + + If `thing` has an `iteritems` method, it is used; otherwise, `thing.items()` is iterated over. + + Parameters: + thing: An iterable object. + + Returns: + iterator: An iterator over the key-value pairs of `thing`. + """ return thing.iteritems() if hasattr(thing, 'iteritems') else iter(thing.items()) def base64encode(s): + """ + Encodes the input string `s` into base64 format. + + Parameters: + s (str): The string to be encoded. + + Returns: + str: The base64 encoded string. + + """ + # Use Python 3 compatible base64 encoding if detected, otherwise use default encoding if is_third(): return str(base64.b64encode(bytes(s, 'utf8')), 'utf8') else: @@ -36,6 +74,15 @@ def tostr(s): def clean_decrypted(s): + """ + Cleans the decrypted string `s` by removing specific control characters. + + Parameters: + s (str): The decrypted string to be cleaned. + + Returns: + str: The cleaned decrypted string. + """ if is_third(): return re.sub(r"[\u0001-\u0010]", '', s).strip() else: diff --git a/ringcentral/deprecated_pubnub_subscription/events.py b/ringcentral/deprecated_pubnub_subscription/events.py index 69584fd..e9a4fc9 100644 --- a/ringcentral/deprecated_pubnub_subscription/events.py +++ b/ringcentral/deprecated_pubnub_subscription/events.py @@ -1,5 +1,18 @@ class Events: + """ + Class representing different types of events that can occur. + + Attributes: + connectionError (str): Represents an event indicating a connection error. + notification (str): Represents an event for receiving notifications. + subscribeSuccess (str): Represents a successful subscription event. + subscribeError (str): Represents an error event during subscription. + renewSuccess (str): Represents a successful renewal event. + renewError (str): Represents an error event during renewal. + removeSuccess (str): Represents a successful removal event. + removeError (str): Represents an error event during removal. + """ connectionError = 'connectionError' notification = 'notification' subscribeSuccess = 'subscribeSuccess' diff --git a/ringcentral/deprecated_pubnub_subscription/subscription.py b/ringcentral/deprecated_pubnub_subscription/subscription.py index f2f9ba1..5932501 100644 --- a/ringcentral/deprecated_pubnub_subscription/subscription.py +++ b/ringcentral/deprecated_pubnub_subscription/subscription.py @@ -34,16 +34,32 @@ def __init__(self, platform): } self._pubnub = None + # Retrieve the PubNub instance associated with this object. def pubnub(self): return self._pubnub def register(self, events=None): + """ + Register for events. If the connection is alive, renews the registration; otherwise, subscribes. + + Args: + events (list, optional): List of events to register for. + + Returns: + obj: Result of renewal or subscription. + """ if self.alive(): return self.renew(events=events) else: return self.subscribe(events=events) def add_events(self, events): + """ + Add additional events to be filtered. + + Args: + events (list): List of events to add. + """ self._event_filters += events pass @@ -51,7 +67,19 @@ def set_events(self, events): self._event_filters = events def subscribe(self, events=None): + """ + Subscribe to events using the defined event filters. + Args: + events (list, optional): List of events to subscribe to. + + Raises: + Exception: If events are undefined. + Exception: If there's an error during subscription Unsubscribe PUBNUB and trigger subscribeError event. + + Returns: + obj: Response object from the subscription request. + """ if events: self.set_events(events) @@ -78,7 +106,20 @@ def subscribe(self, events=None): raise def renew(self, events=None): + """ + Renew the subscription with the current or specified events. + + Args: + events (list, optional): List of events to renew the subscription with. + Raises: + Exception: If the subscription is not alive. + Exception: If events are undefined. + Exception: If there's an error during renewal Unsubscribe PUBNUB and trigger renewError event. + + Returns: + obj: Response object from the renewal request. + """ if events: self.set_events(events) @@ -106,6 +147,16 @@ def renew(self, events=None): raise def remove(self): + """ + Remove the subscription. + + Raises: + Exception: If the subscription is not alive. + Exception: If there's an error during removal Unsubscribe PUBNUB and trigger removeError event. + + Returns: + obj: Response object from the removal request. + """ if not self.alive(): raise Exception('Subscription is not alive') @@ -181,6 +232,20 @@ def _notify(self, message): self.trigger(Events.notification, message) def _decrypt(self, message): + """ + Decrypt the message if encryption is enabled in the subscription. + + Args: + message (str): Encrypted message. + + Raises: + ValueError: If the subscription is not alive. + ImportError: If Crypto.Cipher or required modules are not available. + Exception: If there's an error during decryption. + + Returns: + dict: Decrypted message. + """ if not self.alive(): raise Exception('Subscription is not alive') diff --git a/ringcentral/http/client.py b/ringcentral/http/client.py index bbd1ebc..d461ed9 100644 --- a/ringcentral/http/client.py +++ b/ringcentral/http/client.py @@ -14,6 +14,18 @@ def __init__(self): pass def send(self, request): + """ + Send the HTTP request and handle the response. + + Args: + request (obj): The HTTP request object. + + Returns: + obj: The HTTP response object. + + Raises: + ApiException: If an error occurs during the request or response handling. + """ response = None try: @@ -51,14 +63,19 @@ def load_response(self, request): def create_request(self, method='', url='', query_params=None, body=None, headers=None): """ - :param method: - :param url: - :param query_params: - :param body: - :param headers: - :return:requests.Request - """ + Create an HTTP request object. + + Args: + method (str): The HTTP method (e.g., GET, POST). + url (str): The URL for the request. + query_params (dict, optional): Dictionary containing query parameters. + body (dict, optional): Request body data. + headers (dict, optional): Request headers. + Returns: + requests.Request: The HTTP request object. + + """ if query_params: if type(query_params) is dict: query = "" @@ -75,7 +92,7 @@ def create_request(self, method='', url='', query_params=None, body=None, header url = url + ('&' if url.find('?') > 0 else '?') + query content_type = None - + if headers is None: headers = {} diff --git a/ringcentral/platform/events.py b/ringcentral/platform/events.py index d0d5445..e16257b 100644 --- a/ringcentral/platform/events.py +++ b/ringcentral/platform/events.py @@ -1,5 +1,16 @@ class Events: + """ + Events class representing various event types. + + Attributes: + refreshSuccess (str): Represents a successful refresh event. + refreshError (str): Represents an error during refresh. + loginSuccess (str): Represents a successful login event. + loginError (str): Represents an error during login. + logoutSuccess (str): Represents a successful logout event. + logoutError (str): Represents an error during logout. + """ refreshSuccess = 'refreshSuccess' refreshError = 'refreshError' loginSuccess = 'loginSuccess' diff --git a/ringcentral/platform/platform.py b/ringcentral/platform/platform.py index 26c5c04..f00eb13 100644 --- a/ringcentral/platform/platform.py +++ b/ringcentral/platform/platform.py @@ -63,6 +63,25 @@ def auth(self): return self._auth def create_url(self, url, add_server=False, add_method=None, add_token=False): + """ + Creates a complete URL based on the provided URL and additional parameters. + + Args: + url (str): The base URL. + add_server (bool): Whether to prepend the server URL if the provided URL doesn't contain 'http://' or 'https://'. + add_method (str, optional): The HTTP method to append as a query parameter. + add_token (bool): Whether to append the access token as a query parameter. + + Returns: + str: The complete URL. + + Note: + - If `add_server` is True and the provided URL doesn't start with 'http://' or 'https://', the server URL will be prepended. + - If the provided URL doesn't contain known prefixes or 'http://' or 'https://', the URL_PREFIX and API_VERSION will be appended. + - If the provided URL contains ACCOUNT_PREFIX followed by ACCOUNT_ID, it will be replaced with ACCOUNT_PREFIX and the account ID associated with the SDK instance. + - If `add_method` is provided, it will be appended as a query parameter '_method'. + - If `add_token` is True, the access token associated with the SDK instance will be appended as a query parameter 'access_token'. + """ built_url = '' has_http = url.startswith('http://') or url.startswith('https://') @@ -86,12 +105,35 @@ def create_url(self, url, add_server=False, add_method=None, add_token=False): return built_url def logged_in(self): + """ + Checks if the user is currently logged in. + + Returns: + bool: True if the user is logged in, False otherwise. + + Note: + - This method checks if the access token is valid. + - If the access token is not valid, it attempts to refresh it by calling the `refresh` method. + - If any exceptions occur during the process, it returns False. + """ try: return self._auth.access_token_valid() or self.refresh() except: return False def login_url(self, redirect_uri, state='', challenge='', challenge_method='S256'): + """ + Generates the URL for initiating the login process. + + Args: + redirect_uri (str): The URI to which the user will be redirected after authentication. + state (str, optional): A value to maintain state between the request and the callback. Default is ''. + challenge (str, optional): The code challenge for PKCE (Proof Key for Code Exchange). Default is ''. + challenge_method (str, optional): The code challenge method for PKCE. Default is 'S256'. + + Returns: + str: The login URL. + """ built_url = self.create_url( AUTHORIZE_ENDPOINT, add_server=True ) built_url += '?response_type=code&client_id=' + self._key + '&redirect_uri=' + urllib.parse.quote(redirect_uri) if state: @@ -99,8 +141,35 @@ def login_url(self, redirect_uri, state='', challenge='', challenge_method='S256 if challenge: built_url += '&code_challenge=' + urllib.parse.quote(challenge) + '&code_challenge_method=' + challenge_method return built_url - + def login(self, username='', extension='', password='', code='', redirect_uri='', jwt='', verifier=''): + """ + Logs in the user using various authentication methods. + + Args: + username (str, optional): The username for authentication. Required if password is provided. Default is ''. + extension (str, optional): The extension associated with the username. Default is ''. + password (str, optional): The password for authentication. Required if username is provided. Default is ''. + code (str, optional): The authorization code for authentication. Default is ''. + redirect_uri (str, optional): The URI to redirect to after authentication. Default is ''. + jwt (str, optional): The JWT (JSON Web Token) for authentication. Default is ''. + verifier (str, optional): The code verifier for PKCE (Proof Key for Code Exchange). Default is ''. + + Returns: + Response: The response object containing authentication data if successful. + + Raises: + Exception: If the login attempt fails or invalid parameters are provided. + + Note: + - This method supports multiple authentication flows including password-based, authorization code, and JWT. + - It checks for the presence of required parameters and raises an exception if necessary. + - Deprecation warning is issued for username-password login; recommend using JWT or OAuth instead. + - Constructs the appropriate request body based on the provided parameters. + - Uses `create_url` to build the token endpoint URL, adding the server URL if required. + - Sends the authentication request using `_request_token`. + - Triggers the loginSuccess or loginError event based on the outcome of the login attempt. + """ try: if not code and not username and not password and not jwt: raise Exception('Either code, or username with password, or jwt has to be provided') @@ -140,6 +209,21 @@ def login(self, username='', extension='', password='', code='', redirect_uri='' raise e def refresh(self): + """ + Refreshes the authentication tokens. + + Returns: + Response: The response object containing refreshed authentication data if successful. + + Raises: + Exception: If the refresh token has expired or if any error occurs during the refresh process. + + Note: + - This method checks if the refresh token is still valid using `_auth.refresh_token_valid()`. + - Constructs the request body with the grant type as 'refresh_token' and includes the refresh token. + - Sends the token refresh request using `_request_token` at this '/restapi/oauth/token end point. + - Triggers the refreshSuccess or refreshError event based on the outcome of the refresh attempt. + """ try: if not self._auth.refresh_token_valid(): raise Exception('Refresh token has expired') @@ -157,6 +241,21 @@ def refresh(self): raise e def logout(self): + """ + Logs out the user by revoking the access token. + + Returns: + Response: The response object containing logout confirmation if successful. + + Raises: + Exception: If any error occurs during the logout process. + + Note: + - Constructs the request body with the access token to be revoked. + - Sends the token revoke request using `_request_token` at this /restapi/oauth/revoke end point. + - Resets the authentication data using `_auth.reset()` upon successful logout. + - Triggers the logoutSuccess or logoutError event based on the outcome of the logout attempt. + """ try: response = self._request_token(REVOKE_ENDPOINT, body={ 'token': self._auth.access_token() @@ -169,6 +268,18 @@ def logout(self): raise e def inflate_request(self, request, skip_auth_check=False): + """ + Inflates the provided request object with necessary headers and URL modifications. + + Args: + request (Request): The request object to be inflated. + skip_auth_check (bool, optional): Whether to skip the authentication check and header addition. Default is False. + + Note: + - If `skip_auth_check` is False (default), it ensures authentication by calling `_ensure_authentication` and adds the 'Authorization' header. + - Sets the 'User-Agent' and 'X-User-Agent' headers to the value specified in `_userAgent`. + - Modifies the request URL using `create_url`, adding the server URL if necessary. + """ if not skip_auth_check: self._ensure_authentication() request.headers['Authorization'] = self._auth_header()