From 8faa88fda000ab699248652a31b5b1f006335093 Mon Sep 17 00:00:00 2001 From: Pavel Dubovtsev Date: Mon, 11 Jul 2022 13:28:35 +0500 Subject: [PATCH 1/2] Add ThreadSafeClient implementation --- smpplib/client.py | 77 +++++++++++++++++++++++++++++++++++- smpplib/exceptions.py | 4 ++ smpplib/tests/test_client.py | 18 +++++++-- 3 files changed, 94 insertions(+), 5 deletions(-) diff --git a/smpplib/client.py b/smpplib/client.py index ebad492..06b1ae3 100644 --- a/smpplib/client.py +++ b/smpplib/client.py @@ -19,10 +19,12 @@ """SMPP client module""" import binascii +import collections import logging import select import socket import struct +import time import warnings from smpplib import consts, exceptions, smpp @@ -91,7 +93,6 @@ def __init__( else: self.allow_unknown_opt_params = allow_unknown_opt_params - self._socket = self._create_socket() def __enter__(self): @@ -301,7 +302,7 @@ def set_message_received_handler(self, func): def set_message_sent_handler(self, func): """Set new function to handle message sent event""" self.message_sent_handler = func - + def set_query_resp_handler(self, func): """Set new function to handle query resp event""" self.query_resp_handler = func @@ -421,3 +422,75 @@ def query_message(self, **kwargs): qsm = smpp.make_pdu('query_sm', client=self, **kwargs) self.send_pdu(qsm) return qsm + + +class ThreadSafeClient(Client): + should_stop = False + + def __init__( + self, + *args, + select_timeout=1.0, + **kwargs + ): + super().__init__(*args, **kwargs) + + self._select_timeout = select_timeout + + self._send_queue = collections.deque() + self._read_sock, self._send_sock = socket.socketpair() + + # It will help not to spam the server + self._last_active_time = 0.0 + + def accept(self, obj): + """Accept an object""" + raise NotImplementedError('not implemented') + + def send_pdu(self, pdu, send_later=False) -> bool: + if send_later: + self._send_queue.append(pdu) + self._send_sock.send(b'\x00') + return True + else: + pdu_sent = super().send_pdu(pdu) + self._last_active_time = time.monotonic() + return pdu_sent + + def send_message(self, send_later=True, **kwargs): + submit_sm_pdu = smpp.make_pdu('submit_sm', client=self, **kwargs) + self.send_pdu(submit_sm_pdu, send_later=send_later) + return submit_sm_pdu + + def _should_prolong_session(self): + # We need some time to send enquire_link before the next `select` call comes + passed_from_last_message = time.monotonic() - self._last_active_time + + return self.timeout - self._select_timeout <= passed_from_last_message + + def observe(self, ignore_error_codes=None, auto_send_enquire_link=True) -> None: + while not self.should_stop: + rlist, _, _ = select.select( + [self._socket, self._read_sock], [], [], self._select_timeout, + ) + + if self.should_stop: + break + + if not rlist: + if self._should_prolong_session(): + if not auto_send_enquire_link: + raise exceptions.SessionProlongationDisabled() + + self.logger.debug('Sending enquire_link') + pdu = smpp.make_pdu('enquire_link', client=self) + self.send_pdu(pdu) + else: + for ready_socket in rlist: + if ready_socket is self._socket: + self.read_once(ignore_error_codes, auto_send_enquire_link) + else: + self._read_sock.recv(1) + self.send_pdu(self._send_queue.pop()) + + self.logger.info('Finished observing...') diff --git a/smpplib/exceptions.py b/smpplib/exceptions.py index 8f06f64..a6ddc55 100644 --- a/smpplib/exceptions.py +++ b/smpplib/exceptions.py @@ -17,3 +17,7 @@ class PDUError(RuntimeError): class MessageTooLong(ValueError): """Text too long to fit 255 SMS""" + + +class SessionProlongationDisabled(Exception): + """Server send nothing and we do not want to continue""" diff --git a/smpplib/tests/test_client.py b/smpplib/tests/test_client.py index a10666f..42afff1 100644 --- a/smpplib/tests/test_client.py +++ b/smpplib/tests/test_client.py @@ -1,11 +1,13 @@ +import time import warnings + import pytest -from mock import Mock, call +from mock import call, Mock -from smpplib.client import Client -from smpplib.smpp import make_pdu from smpplib import consts from smpplib import exceptions +from smpplib.client import Client, ThreadSafeClient +from smpplib.smpp import make_pdu def test_client_construction_allow_unknown_opt_params_warning(): @@ -44,3 +46,13 @@ def test_client_error_pdu_custom_handler(): client.read_once() assert mock_error_pdu_handler.mock_calls == [call(error_pdu)] + + +def test_prolongation(): + client = ThreadSafeClient("localhost", 5679) + client._last_active_time = time.monotonic() + assert not client._should_prolong_session() + + time.sleep(client.timeout - client._select_timeout) + + assert client._should_prolong_session() From 455c19dac3f518aaa9c359f6f20e8c52be0d3ee7 Mon Sep 17 00:00:00 2001 From: Pavel Dubovtsev Date: Tue, 25 Apr 2023 18:52:11 +0500 Subject: [PATCH 2/2] python2.7 compat --- setup.py | 2 +- smpplib/client.py | 24 +++++++++++------------- smpplib/tests/test_client.py | 3 ++- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/setup.py b/setup.py index 36420e9..8846128 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ url='https://github.com/python-smpplib/python-smpplib', description='SMPP library for python', packages=find_packages(), - install_requires=['six'], + install_requires=['six', 'monotonic'], extras_require=dict( tests=('typing; python_version < "3.5"', 'pytest', 'mock'), ), diff --git a/smpplib/client.py b/smpplib/client.py index 06b1ae3..c3825d0 100644 --- a/smpplib/client.py +++ b/smpplib/client.py @@ -24,9 +24,9 @@ import select import socket import struct -import time import warnings +from monotonic import monotonic from smpplib import consts, exceptions, smpp @@ -427,13 +427,11 @@ def query_message(self, **kwargs): class ThreadSafeClient(Client): should_stop = False - def __init__( - self, - *args, - select_timeout=1.0, - **kwargs - ): - super().__init__(*args, **kwargs) + def __init__(self, *args, **kwargs): + # Socket polling period + select_timeout = kwargs.get('select_timeout', 1.0) + + super(ThreadSafeClient, self).__init__(*args, **kwargs) self._select_timeout = select_timeout @@ -447,14 +445,14 @@ def accept(self, obj): """Accept an object""" raise NotImplementedError('not implemented') - def send_pdu(self, pdu, send_later=False) -> bool: + def send_pdu(self, pdu, send_later=False): if send_later: self._send_queue.append(pdu) self._send_sock.send(b'\x00') return True else: - pdu_sent = super().send_pdu(pdu) - self._last_active_time = time.monotonic() + pdu_sent = super(ThreadSafeClient, self).send_pdu(pdu) + self._last_active_time = monotonic() return pdu_sent def send_message(self, send_later=True, **kwargs): @@ -464,11 +462,11 @@ def send_message(self, send_later=True, **kwargs): def _should_prolong_session(self): # We need some time to send enquire_link before the next `select` call comes - passed_from_last_message = time.monotonic() - self._last_active_time + passed_from_last_message = monotonic() - self._last_active_time return self.timeout - self._select_timeout <= passed_from_last_message - def observe(self, ignore_error_codes=None, auto_send_enquire_link=True) -> None: + def observe(self, ignore_error_codes=None, auto_send_enquire_link=True): while not self.should_stop: rlist, _, _ = select.select( [self._socket, self._read_sock], [], [], self._select_timeout, diff --git a/smpplib/tests/test_client.py b/smpplib/tests/test_client.py index 42afff1..f2580c0 100644 --- a/smpplib/tests/test_client.py +++ b/smpplib/tests/test_client.py @@ -3,6 +3,7 @@ import pytest from mock import call, Mock +from monotonic import monotonic from smpplib import consts from smpplib import exceptions @@ -50,7 +51,7 @@ def test_client_error_pdu_custom_handler(): def test_prolongation(): client = ThreadSafeClient("localhost", 5679) - client._last_active_time = time.monotonic() + client._last_active_time = monotonic() assert not client._should_prolong_session() time.sleep(client.timeout - client._select_timeout)