From df015c45ec757aa382384bbf95bf9d33a59d798b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20D=C4=99bski?= <m.debski@livechat.com> Date: Wed, 10 Jan 2024 14:46:09 +0100 Subject: [PATCH 1/3] Enhanced timeouts for the WebsocketClient --- changelog.md | 1 + livechat/utils/ws_client.py | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 62cdba7..28bb5a9 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. ### Changed - Updated outdated packages. - Enhanced error logging for improved troubleshooting: Automatically includes response headers in the log for server errors, providing detailed information (such as x-debug-id) for more effective issue diagnosis. +- Enhanced timeouts for the `WebsocketClient`. ### Bugfixes - Enabled instantiation for `CustomerRtmV36` within the 3.6 version of the Customer RTM API. diff --git a/livechat/utils/ws_client.py b/livechat/utils/ws_client.py index 06f938f..1f18607 100644 --- a/livechat/utils/ws_client.py +++ b/livechat/utils/ws_client.py @@ -32,26 +32,33 @@ def __init__(self, *args, **kwargs): def open(self, origin: dict = None, - timeout: float = 3, + ping_timeout: float = 3, + ping_interval: float = 5, + ws_conn_timeout: float = 10, keep_alive: bool = True) -> NoReturn: ''' Opens websocket connection and keep running forever. Args: origin (dict): Specifies origin while creating websocket connection. - timeout (int or float): time [seconds] to wait for server in ping/pong frame. + ping_timeout (int or float): timeout (in seconds) if the pong message is not received, + by default sets to 3 seconds. + ping_interval (int or float): automatically sends "ping" command every specified period (in seconds). + If set to 0, no ping is sent periodically, by default sets to 5 seconds. + ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection, + by default sets to 10 seconds. keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`. ''' run_forever_kwargs = { 'sslopt': { 'cert_reqs': ssl.CERT_NONE }, 'origin': origin, - 'ping_timeout': timeout, - 'ping_interval': 5 + 'ping_timeout': ping_timeout, + 'ping_interval': ping_interval, } if keep_alive: ping_thread = threading.Thread(target=self.run_forever, kwargs=run_forever_kwargs) ping_thread.start() - self._wait_till_sock_connected() + self._wait_till_sock_connected(ws_conn_timeout) return self.run_forever(**run_forever_kwargs) From dfc4f8a304399063a1900b041ccef3918e94dc93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20D=C4=99bski?= <m.debski@livechat.com> Date: Thu, 11 Jan 2024 13:40:58 +0100 Subject: [PATCH 2/3] Enhanced timeouts for the WebsocketClient part2 --- livechat/agent/rtm/api/v33.py | 29 +++++++++++++++++++++++++---- livechat/agent/rtm/api/v34.py | 29 +++++++++++++++++++++++++---- livechat/agent/rtm/api/v35.py | 29 +++++++++++++++++++++++++---- livechat/agent/rtm/api/v36.py | 23 ++++++++++++++++++++--- livechat/agent/web/api/v33.py | 5 +++-- livechat/agent/web/api/v34.py | 5 +++-- livechat/agent/web/api/v35.py | 5 +++-- livechat/agent/web/api/v36.py | 5 +++-- livechat/agent/web/base.py | 13 +++++++++---- livechat/billing/api/v1.py | 5 +++-- livechat/billing/base.py | 7 ++++++- livechat/configuration/api/v33.py | 5 +++-- livechat/configuration/api/v34.py | 5 +++-- livechat/configuration/api/v35.py | 5 +++-- livechat/configuration/api/v36.py | 5 +++-- livechat/configuration/base.py | 13 +++++++++---- livechat/customer/rtm/api/v33.py | 21 ++++++++++++++++----- livechat/customer/rtm/api/v34.py | 21 ++++++++++++++++----- livechat/customer/rtm/api/v35.py | 21 ++++++++++++++++----- livechat/customer/rtm/api/v36.py | 21 ++++++++++++++++----- livechat/customer/web/api/v33.py | 5 +++-- livechat/customer/web/api/v34.py | 5 +++-- livechat/customer/web/api/v35.py | 5 +++-- livechat/customer/web/api/v36.py | 5 +++-- livechat/customer/web/base.py | 17 +++++++++++++---- livechat/reports/api/v33.py | 5 +++-- livechat/reports/api/v34.py | 5 +++-- livechat/reports/api/v35.py | 5 +++-- livechat/reports/api/v36.py | 5 +++-- livechat/reports/base.py | 13 +++++++++---- 30 files changed, 256 insertions(+), 86 deletions(-) diff --git a/livechat/agent/rtm/api/v33.py b/livechat/agent/rtm/api/v33.py index 028feff..4a9766e 100644 --- a/livechat/agent/rtm/api/v33.py +++ b/livechat/agent/rtm/api/v33.py @@ -14,9 +14,26 @@ class AgentRtmV33: def __init__(self, url: str): self.ws = WebsocketClient(url=f'wss://{url}/v3.3/agent/rtm/ws') - def open_connection(self) -> None: - ''' Opens WebSocket connection. ''' - self.ws.open() + def open_connection(self, + origin: dict = None, + ping_timeout: float = 3, + ping_interval: float = 5, + ws_conn_timeout: float = 10, + keep_alive: bool = True) -> None: + ''' Opens WebSocket connection. + + Args: + origin (dict): Specifies origin while creating websocket connection. + ping_timeout (int or float): timeout (in seconds) if the pong message is not received, + by default sets to 3 seconds. + ping_interval (int or float): automatically sends "ping" command every specified period (in seconds). + If set to 0, no ping is sent periodically, by default sets to 5 seconds. + ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection, + by default sets to 10 seconds. + keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`. + ''' + self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout, + keep_alive) def close_connection(self) -> None: ''' Closes WebSocket connection. ''' @@ -378,7 +395,11 @@ def send_event(self, opts['author_id'] = author_id if payload is None: payload = prepare_payload(locals()) - return self.ws.send({'action': 'send_event', 'payload': payload, **opts}) + return self.ws.send({ + 'action': 'send_event', + 'payload': payload, + **opts + }) def send_rich_message_postback(self, chat_id: str = None, diff --git a/livechat/agent/rtm/api/v34.py b/livechat/agent/rtm/api/v34.py index 1fca262..7c24903 100644 --- a/livechat/agent/rtm/api/v34.py +++ b/livechat/agent/rtm/api/v34.py @@ -14,9 +14,26 @@ class AgentRtmV34: def __init__(self, url: str): self.ws = WebsocketClient(url=f'wss://{url}/v3.4/agent/rtm/ws') - def open_connection(self) -> None: - ''' Opens WebSocket connection. ''' - self.ws.open() + def open_connection(self, + origin: dict = None, + ping_timeout: float = 3, + ping_interval: float = 5, + ws_conn_timeout: float = 10, + keep_alive: bool = True) -> None: + ''' Opens WebSocket connection. + + Args: + origin (dict): Specifies origin while creating websocket connection. + ping_timeout (int or float): timeout (in seconds) if the pong message is not received, + by default sets to 3 seconds. + ping_interval (int or float): automatically sends "ping" command every specified period (in seconds). + If set to 0, no ping is sent periodically, by default sets to 5 seconds. + ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection, + by default sets to 10 seconds. + keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`. + ''' + self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout, + keep_alive) def close_connection(self) -> None: ''' Closes WebSocket connection. ''' @@ -344,7 +361,11 @@ def send_event(self, opts['author_id'] = author_id if payload is None: payload = prepare_payload(locals()) - return self.ws.send({'action': 'send_event', 'payload': payload, **opts}) + return self.ws.send({ + 'action': 'send_event', + 'payload': payload, + **opts + }) def send_rich_message_postback(self, chat_id: str = None, diff --git a/livechat/agent/rtm/api/v35.py b/livechat/agent/rtm/api/v35.py index 7ee5934..76cb371 100644 --- a/livechat/agent/rtm/api/v35.py +++ b/livechat/agent/rtm/api/v35.py @@ -14,9 +14,26 @@ class AgentRtmV35: def __init__(self, url: str): self.ws = WebsocketClient(url=f'wss://{url}/v3.5/agent/rtm/ws') - def open_connection(self) -> None: - ''' Opens WebSocket connection. ''' - self.ws.open() + def open_connection(self, + origin: dict = None, + ping_timeout: float = 3, + ping_interval: float = 5, + ws_conn_timeout: float = 10, + keep_alive: bool = True) -> None: + ''' Opens WebSocket connection. + + Args: + origin (dict): Specifies origin while creating websocket connection. + ping_timeout (int or float): timeout (in seconds) if the pong message is not received, + by default sets to 3 seconds. + ping_interval (int or float): automatically sends "ping" command every specified period (in seconds). + If set to 0, no ping is sent periodically, by default sets to 5 seconds. + ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection, + by default sets to 10 seconds. + keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`. + ''' + self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout, + keep_alive) def close_connection(self) -> None: ''' Closes WebSocket connection. ''' @@ -344,7 +361,11 @@ def send_event(self, opts['author_id'] = author_id if payload is None: payload = prepare_payload(locals()) - return self.ws.send({'action': 'send_event', 'payload': payload, **opts}) + return self.ws.send({ + 'action': 'send_event', + 'payload': payload, + **opts + }) def send_rich_message_postback(self, chat_id: str = None, diff --git a/livechat/agent/rtm/api/v36.py b/livechat/agent/rtm/api/v36.py index c253460..7885997 100644 --- a/livechat/agent/rtm/api/v36.py +++ b/livechat/agent/rtm/api/v36.py @@ -14,9 +14,26 @@ class AgentRtmV36: def __init__(self, url: str): self.ws = WebsocketClient(url=f'wss://{url}/v3.6/agent/rtm/ws') - def open_connection(self) -> None: - ''' Opens WebSocket connection. ''' - self.ws.open() + def open_connection(self, + origin: dict = None, + ping_timeout: float = 3, + ping_interval: float = 5, + ws_conn_timeout: float = 10, + keep_alive: bool = True) -> None: + ''' Opens WebSocket connection. + + Args: + origin (dict): Specifies origin while creating websocket connection. + ping_timeout (int or float): timeout (in seconds) if the pong message is not received, + by default sets to 3 seconds. + ping_interval (int or float): automatically sends "ping" command every specified period (in seconds). + If set to 0, no ping is sent periodically, by default sets to 5 seconds. + ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection, + by default sets to 10 seconds. + keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`. + ''' + self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout, + keep_alive) def close_connection(self) -> None: ''' Closes WebSocket connection. ''' diff --git a/livechat/agent/web/api/v33.py b/livechat/agent/web/api/v33.py index 3b5d840..0ca4c13 100644 --- a/livechat/agent/web/api/v33.py +++ b/livechat/agent/web/api/v33.py @@ -19,9 +19,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(access_token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v3.3/agent/action' # Chats diff --git a/livechat/agent/web/api/v34.py b/livechat/agent/web/api/v34.py index 61d4dac..03b756b 100644 --- a/livechat/agent/web/api/v34.py +++ b/livechat/agent/web/api/v34.py @@ -21,9 +21,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(access_token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v3.4/agent/action' # Chats diff --git a/livechat/agent/web/api/v35.py b/livechat/agent/web/api/v35.py index 1538cba..6196f78 100644 --- a/livechat/agent/web/api/v35.py +++ b/livechat/agent/web/api/v35.py @@ -21,9 +21,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(access_token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v3.5/agent/action' # Chats diff --git a/livechat/agent/web/api/v36.py b/livechat/agent/web/api/v36.py index 1a014b3..e25e242 100644 --- a/livechat/agent/web/api/v36.py +++ b/livechat/agent/web/api/v36.py @@ -21,9 +21,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(access_token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v3.6/agent/action' # Chats diff --git a/livechat/agent/web/base.py b/livechat/agent/web/base.py index 6f181a8..18f1eb0 100644 --- a/livechat/agent/web/base.py +++ b/livechat/agent/web/base.py @@ -5,6 +5,8 @@ from typing import Union +import httpx + from livechat.agent.web.api.v33 import AgentWebV33 from livechat.agent.web.api.v34 import AgentWebV34 from livechat.agent.web.api.v35 import AgentWebV35 @@ -27,6 +29,7 @@ def get_client( proxies: dict = None, verify: bool = True, disable_logging: bool = False, + timeout: float = httpx.Timeout(15) ) -> Union[AgentWebV33, AgentWebV34, AgentWebV35, AgentWebV36]: ''' Returns client for specific API version. @@ -43,6 +46,8 @@ def get_client( a path to an SSL certificate file, an `ssl.SSLContext`, or `False` (which will disable verification). Defaults to `True`. disable_logging (bool): indicates if logging should be disabled. + timeout (float): The timeout configuration to use when sending requests. + Defaults to 15 seconds. Returns: API client object for specified version. @@ -53,16 +58,16 @@ def get_client( client = { '3.3': AgentWebV33(access_token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), '3.4': AgentWebV34(access_token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), '3.5': AgentWebV35(access_token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), '3.6': AgentWebV36(access_token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), }.get(version) if not client: raise ValueError('Provided version does not exist.') diff --git a/livechat/billing/api/v1.py b/livechat/billing/api/v1.py index 9b8ca22..b2a19ee 100644 --- a/livechat/billing/api/v1.py +++ b/livechat/billing/api/v1.py @@ -14,9 +14,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v1' # direct_charge diff --git a/livechat/billing/base.py b/livechat/billing/base.py index 00cf548..a54390d 100644 --- a/livechat/billing/base.py +++ b/livechat/billing/base.py @@ -5,6 +5,8 @@ from __future__ import annotations +import httpx + from livechat.config import CONFIG from .api import BillingApiV1 @@ -25,6 +27,7 @@ def get_client( proxies: dict = None, verify: bool = True, disable_logging: bool = False, + timeout: float = httpx.Timeout(15) ) -> BillingApiV1: ''' Returns client for specific Billing API version. @@ -41,6 +44,8 @@ def get_client( a path to an SSL certificate file, an `ssl.SSLContext`, or `False` (which will disable verification). Defaults to `True`. disable_logging (bool): indicates if logging should be disabled. + timeout (float): The timeout configuration to use when sending requests. + Defaults to 15 seconds. Returns: BillingApi: API client object for specified version. @@ -51,7 +56,7 @@ def get_client( client = { '1': BillingApiV1(token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), }.get(version) if not client: raise ValueError('Provided version does not exist.') diff --git a/livechat/configuration/api/v33.py b/livechat/configuration/api/v33.py index 0829a5c..9efec0c 100644 --- a/livechat/configuration/api/v33.py +++ b/livechat/configuration/api/v33.py @@ -14,9 +14,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v3.3/configuration/action' # Agents diff --git a/livechat/configuration/api/v34.py b/livechat/configuration/api/v34.py index 19764bc..04c3461 100644 --- a/livechat/configuration/api/v34.py +++ b/livechat/configuration/api/v34.py @@ -14,9 +14,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v3.4/configuration/action' # Agents diff --git a/livechat/configuration/api/v35.py b/livechat/configuration/api/v35.py index c214b54..1e3d454 100644 --- a/livechat/configuration/api/v35.py +++ b/livechat/configuration/api/v35.py @@ -16,9 +16,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v3.5/configuration/action' # Agents diff --git a/livechat/configuration/api/v36.py b/livechat/configuration/api/v36.py index 1a0b33e..5a72e2b 100644 --- a/livechat/configuration/api/v36.py +++ b/livechat/configuration/api/v36.py @@ -18,9 +18,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v3.6/configuration/action' # Agents diff --git a/livechat/configuration/base.py b/livechat/configuration/base.py index bf6e45c..9b95faa 100644 --- a/livechat/configuration/base.py +++ b/livechat/configuration/base.py @@ -6,6 +6,8 @@ from typing import Union +import httpx + from livechat.config import CONFIG from livechat.configuration.api.v33 import ConfigurationApiV33 from livechat.configuration.api.v34 import ConfigurationApiV34 @@ -28,6 +30,7 @@ def get_client( proxies: dict = None, verify: bool = True, disable_logging: bool = False, + timeout: float = httpx.Timeout(15) ) -> Union[ConfigurationApiV33, ConfigurationApiV34, ConfigurationApiV35, ConfigurationApiV36]: ''' Returns client for specific Configuration API version. @@ -45,6 +48,8 @@ def get_client( a path to an SSL certificate file, an `ssl.SSLContext`, or `False` (which will disable verification). Defaults to `True`. disable_logging (bool): indicates if logging should be disabled. + timeout (float): The timeout configuration to use when sending requests. + Defaults to 15 seconds. Returns: ConfigurationApi: API client object for specified version. @@ -55,16 +60,16 @@ def get_client( client = { '3.3': ConfigurationApiV33(token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), '3.4': ConfigurationApiV34(token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), '3.5': ConfigurationApiV35(token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), '3.6': ConfigurationApiV36(token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), }.get(version) if not client: raise ValueError('Provided version does not exist.') diff --git a/livechat/customer/rtm/api/v33.py b/livechat/customer/rtm/api/v33.py index a6929fe..b877b23 100644 --- a/livechat/customer/rtm/api/v33.py +++ b/livechat/customer/rtm/api/v33.py @@ -20,15 +20,26 @@ def __init__(self, license_id: str, base_url: str): f'Provided `license_id` (`{license_id}`) seems invalid. Websocket connection may not open.' ) - def open_connection(self, origin: dict = None) -> None: + def open_connection(self, + origin: dict = None, + ping_timeout: float = 3, + ping_interval: float = 5, + ws_conn_timeout: float = 10, + keep_alive: bool = True) -> None: ''' Opens WebSocket connection. + Args: origin (dict): Specifies origin while creating websocket connection. + ping_timeout (int or float): timeout (in seconds) if the pong message is not received, + by default sets to 3 seconds. + ping_interval (int or float): automatically sends "ping" command every specified period (in seconds). + If set to 0, no ping is sent periodically, by default sets to 5 seconds. + ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection, + by default sets to 10 seconds. + keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`. ''' - if origin: - self.ws.open(origin=origin) - else: - self.ws.open() + self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout, + keep_alive) def close_connection(self) -> None: ''' Closes WebSocket connection. ''' diff --git a/livechat/customer/rtm/api/v34.py b/livechat/customer/rtm/api/v34.py index b0d3edb..b61a2e3 100644 --- a/livechat/customer/rtm/api/v34.py +++ b/livechat/customer/rtm/api/v34.py @@ -20,15 +20,26 @@ def __init__(self, organization_id: str, base_url: str): f'Provided `organization_id` (`{organization_id}`) seems invalid. Websocket connection may not open.' ) - def open_connection(self, origin: dict = None) -> None: + def open_connection(self, + origin: dict = None, + ping_timeout: float = 3, + ping_interval: float = 5, + ws_conn_timeout: float = 10, + keep_alive: bool = True) -> None: ''' Opens WebSocket connection. + Args: origin (dict): Specifies origin while creating websocket connection. + ping_timeout (int or float): timeout (in seconds) if the pong message is not received, + by default sets to 3 seconds. + ping_interval (int or float): automatically sends "ping" command every specified period (in seconds). + If set to 0, no ping is sent periodically, by default sets to 5 seconds. + ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection, + by default sets to 10 seconds. + keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`. ''' - if origin: - self.ws.open(origin=origin) - else: - self.ws.open() + self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout, + keep_alive) def close_connection(self) -> None: ''' Closes WebSocket connection. ''' diff --git a/livechat/customer/rtm/api/v35.py b/livechat/customer/rtm/api/v35.py index 749befe..563b3d0 100644 --- a/livechat/customer/rtm/api/v35.py +++ b/livechat/customer/rtm/api/v35.py @@ -20,15 +20,26 @@ def __init__(self, organization_id: str, base_url: str): f'Provided `organization_id` (`{organization_id}`) seems invalid. Websocket connection may not open.' ) - def open_connection(self, origin: dict = None) -> None: + def open_connection(self, + origin: dict = None, + ping_timeout: float = 3, + ping_interval: float = 5, + ws_conn_timeout: float = 10, + keep_alive: bool = True) -> None: ''' Opens WebSocket connection. + Args: origin (dict): Specifies origin while creating websocket connection. + ping_timeout (int or float): timeout (in seconds) if the pong message is not received, + by default sets to 3 seconds. + ping_interval (int or float): automatically sends "ping" command every specified period (in seconds). + If set to 0, no ping is sent periodically, by default sets to 5 seconds. + ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection, + by default sets to 10 seconds. + keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`. ''' - if origin: - self.ws.open(origin=origin) - else: - self.ws.open() + self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout, + keep_alive) def close_connection(self) -> None: ''' Closes WebSocket connection. ''' diff --git a/livechat/customer/rtm/api/v36.py b/livechat/customer/rtm/api/v36.py index 17df9fa..6255605 100644 --- a/livechat/customer/rtm/api/v36.py +++ b/livechat/customer/rtm/api/v36.py @@ -20,15 +20,26 @@ def __init__(self, organization_id: str, base_url: str): f'Provided `organization_id` (`{organization_id}`) seems invalid. Websocket connection may not open.' ) - def open_connection(self, origin: dict = None) -> None: + def open_connection(self, + origin: dict = None, + ping_timeout: float = 3, + ping_interval: float = 5, + ws_conn_timeout: float = 10, + keep_alive: bool = True) -> None: ''' Opens WebSocket connection. + Args: origin (dict): Specifies origin while creating websocket connection. + ping_timeout (int or float): timeout (in seconds) if the pong message is not received, + by default sets to 3 seconds. + ping_interval (int or float): automatically sends "ping" command every specified period (in seconds). + If set to 0, no ping is sent periodically, by default sets to 5 seconds. + ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection, + by default sets to 10 seconds. + keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`. ''' - if origin: - self.ws.open(origin=origin) - else: - self.ws.open() + self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout, + keep_alive) def close_connection(self) -> None: ''' Closes WebSocket connection. ''' diff --git a/livechat/customer/web/api/v33.py b/livechat/customer/web/api/v33.py index 70f6117..7403c42 100644 --- a/livechat/customer/web/api/v33.py +++ b/livechat/customer/web/api/v33.py @@ -18,10 +18,11 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): if all([access_token, isinstance(access_token, str)]): super().__init__(access_token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) else: raise ValueError( 'Incorrect or missing `access_token` argument (should be of type str.)' diff --git a/livechat/customer/web/api/v34.py b/livechat/customer/web/api/v34.py index 4cc8c37..c6b54ff 100644 --- a/livechat/customer/web/api/v34.py +++ b/livechat/customer/web/api/v34.py @@ -18,10 +18,11 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): if all([access_token, isinstance(access_token, str)]): super().__init__(access_token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) else: raise ValueError( 'Incorrect or missing `access_token` argument (should be of type str.)' diff --git a/livechat/customer/web/api/v35.py b/livechat/customer/web/api/v35.py index 3dfe03c..dd63d73 100644 --- a/livechat/customer/web/api/v35.py +++ b/livechat/customer/web/api/v35.py @@ -18,10 +18,11 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): if all([access_token, isinstance(access_token, str)]): super().__init__(access_token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) else: raise ValueError( 'Incorrect or missing `access_token` argument (should be of type str.)' diff --git a/livechat/customer/web/api/v36.py b/livechat/customer/web/api/v36.py index 327fcd1..401aa58 100644 --- a/livechat/customer/web/api/v36.py +++ b/livechat/customer/web/api/v36.py @@ -18,10 +18,11 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): if all([access_token, isinstance(access_token, str)]): super().__init__(access_token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) else: raise ValueError( 'Incorrect or missing `access_token` argument (should be of type str.)' diff --git a/livechat/customer/web/base.py b/livechat/customer/web/base.py index 3e5c8ed..a403f24 100644 --- a/livechat/customer/web/base.py +++ b/livechat/customer/web/base.py @@ -5,6 +5,8 @@ from typing import Union +import httpx + from livechat.config import CONFIG from livechat.customer.web.api.v33 import CustomerWebV33 from livechat.customer.web.api.v34 import CustomerWebV34 @@ -30,6 +32,7 @@ def get_client( verify: bool = True, organization_id: str = None, disable_logging: bool = False, + timeout: float = httpx.Timeout(15) ) -> Union[CustomerWebV33, CustomerWebV34, CustomerWebV35, CustomerWebV36]: ''' Returns client for specific API version. @@ -48,6 +51,8 @@ def get_client( (which will disable verification). Defaults to `True`. organization_id (str): Organization ID, replaced license ID in v3.4. disable_logging (bool): indicates if logging should be disabled. + timeout (float): The timeout configuration to use when sending requests. + Defaults to 15 seconds. Returns: API client object for specified version based on @@ -70,7 +75,8 @@ def get_client( 'http2': http2, 'proxies': proxies, 'verify': verify, - 'disable_logging': disable_logging + 'disable_logging': disable_logging, + 'timeout': timeout }, '3.4': { 'organization_id': organization_id, @@ -79,7 +85,8 @@ def get_client( 'http2': http2, 'proxies': proxies, 'verify': verify, - 'disable_logging': disable_logging + 'disable_logging': disable_logging, + 'timeout': timeout }, '3.5': { 'organization_id': organization_id, @@ -88,7 +95,8 @@ def get_client( 'http2': http2, 'proxies': proxies, 'verify': verify, - 'disable_logging': disable_logging + 'disable_logging': disable_logging, + 'timeout': timeout }, '3.6': { 'organization_id': organization_id, @@ -97,7 +105,8 @@ def get_client( 'http2': http2, 'proxies': proxies, 'verify': verify, - 'disable_logging': disable_logging + 'disable_logging': disable_logging, + 'timeout': timeout }, }.get(version) if client: diff --git a/livechat/reports/api/v33.py b/livechat/reports/api/v33.py index e755581..56d96a8 100644 --- a/livechat/reports/api/v33.py +++ b/livechat/reports/api/v33.py @@ -14,9 +14,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v3.3/reports' # Chats diff --git a/livechat/reports/api/v34.py b/livechat/reports/api/v34.py index 28defb2..94c4765 100644 --- a/livechat/reports/api/v34.py +++ b/livechat/reports/api/v34.py @@ -14,9 +14,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v3.4/reports' # Chats diff --git a/livechat/reports/api/v35.py b/livechat/reports/api/v35.py index 18dbec9..caa3566 100644 --- a/livechat/reports/api/v35.py +++ b/livechat/reports/api/v35.py @@ -14,9 +14,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v3.5/reports' # Chats diff --git a/livechat/reports/api/v36.py b/livechat/reports/api/v36.py index 2117b20..9998d4f 100644 --- a/livechat/reports/api/v36.py +++ b/livechat/reports/api/v36.py @@ -14,9 +14,10 @@ def __init__(self, http2: bool, proxies=None, verify: bool = True, - disable_logging: bool = False): + disable_logging: bool = False, + timeout: float = httpx.Timeout(15)): super().__init__(token, base_url, http2, proxies, verify, - disable_logging) + disable_logging, timeout) self.api_url = f'https://{base_url}/v3.6/reports' # Chats diff --git a/livechat/reports/base.py b/livechat/reports/base.py index 6761e0b..5ed134e 100644 --- a/livechat/reports/base.py +++ b/livechat/reports/base.py @@ -7,6 +7,8 @@ from typing import Union +import httpx + from livechat.config import CONFIG from livechat.reports.api.v33 import ReportsApiV33 from livechat.reports.api.v34 import ReportsApiV34 @@ -29,6 +31,7 @@ def get_client( proxies: dict = None, verify: bool = True, disable_logging: bool = False, + timeout: float = httpx.Timeout(15) ) -> Union[ReportsApiV33, ReportsApiV34, ReportsApiV35, ReportsApiV36]: ''' Returns client for specific Reports API version. @@ -45,6 +48,8 @@ def get_client( a path to an SSL certificate file, an `ssl.SSLContext`, or `False` (which will disable verification). Defaults to `True`. disable_logging (bool): indicates if logging should be disabled. + timeout (float): The timeout configuration to use when sending requests. + Defaults to 15 seconds. Returns: ReportsApi: API client object for specified version. @@ -55,16 +60,16 @@ def get_client( client = { '3.3': ReportsApiV33(token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), '3.4': ReportsApiV34(token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), '3.5': ReportsApiV35(token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), '3.6': ReportsApiV36(token, base_url, http2, proxies, verify, - disable_logging), + disable_logging, timeout), }.get(version) if not client: raise ValueError('Provided version does not exist.') From 138f9d74cb02287cd1ec6dfec0ad62a637259805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20D=C4=99bski?= <m.debski@livechat.com> Date: Thu, 11 Jan 2024 13:43:41 +0100 Subject: [PATCH 3/3] Updated changelog --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 28bb5a9..51fd980 100644 --- a/changelog.md +++ b/changelog.md @@ -9,7 +9,7 @@ All notable changes to this project will be documented in this file. ### Changed - Updated outdated packages. - Enhanced error logging for improved troubleshooting: Automatically includes response headers in the log for server errors, providing detailed information (such as x-debug-id) for more effective issue diagnosis. -- Enhanced timeouts for the `WebsocketClient`. +- Enhanced timeouts for the RTM and WEB clients. ### Bugfixes - Enabled instantiation for `CustomerRtmV36` within the 3.6 version of the Customer RTM API.