123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- #
- """
- helpers (Helpers)
- =================
-
- **Author:**
-
- * Dirk Alders <sudo-dirk@mount-mockery.de>
-
- **Description:**
-
- This module supports functions and classes which don't have an own Module (yet).
-
- **Submodules:**
-
- * :class:`helpers.continues_statistic`
- * :class:`helpers.continues_statistic_multivalue`
- * :class:`helpers.direct_socket_client`
- * :class:`helpers.direct_socket_server`
- * :class:`helpers.ringbuffer`
-
- **Unittest:**
-
- See also the :download:`unittest <helpers/_testresults_/unittest.pdf>` documentation.
-
- **Module Documentation:**
-
- """
- import logging
- import numbers
- import os
- import time
-
- import stringtools
- import task
- __DEPENDENCIES__ = ['stringtools', 'task', ]
-
- try:
- from config import APP_NAME as ROOT_LOGGER_NAME
- except ImportError:
- ROOT_LOGGER_NAME = 'root'
-
- logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
-
-
- __DESCRIPTION__ = """The Module {\\tt %s} is designed to help with some function which don't have an own Module (yet).
- For more Information read the documentation.""" % __name__.replace('_', '\_')
- """The Module Description"""
- __INTERPRETER__ = (2, 3)
- """The Tested Interpreter-Versions"""
-
-
- class continues_statistic(dict):
- """
- This class stores a statistic for a stream of values. The statistic includes: mean, min, max, quantifier.
-
- .. note:: You can use the mathematic operators "+, +=" to add a value to the statistic.
-
- :param mean: The mean start value, the start value or None.
- :type mean: numeric
- :param min_val: The min start value or None
- :type min_val: numeric
- :param max_val: The max start value or None
- :type max_val: numeric
- :param quantifier: The quantifier start value or 0
- :type quantifier: int
-
- .. note:: You are able to initialise this class in the following ways:
-
- * Without arguments to get an empty instance
- * With one single value to get an starting instance
- * With mean, min_val, max_val, quantifier to initialise an existing statistic
-
- **Example:**
-
- .. literalinclude:: helpers/_examples_/continues_statistic.py
-
- Will result to the following output:
-
- .. literalinclude:: helpers/_examples_/continues_statistic.log
- """
- def __init__(self, mean=None, min_val=None, max_val=None, quantifier=0):
- dict.__init__(self)
- if mean is None:
- self.__init_data__(None, None, None, 0)
- elif min_val is not None and max_val is not None and quantifier > 0:
- self.__init_data__(mean, min_val, max_val, quantifier)
- else:
- self.__init_data__(mean, mean, mean, 1)
-
- def __init_data__(self, mean, min_val, max_val, quantifier):
- self['quantifier'] = quantifier
- self['max_val'] = max_val
- self['min_val'] = min_val
- self['mean'] = mean
-
- def __add__(self, other):
- if self.quantifier == 0:
- return continues_statistic(**other)
- elif other.quantifier == 0:
- return continues_statistic(**self)
- else:
- return continues_statistic(
- mean=(self.mean * self.quantifier + other.mean * other.quantifier) / (self.quantifier + other.quantifier),
- min_val=min(self.min, other.min),
- max_val=max(self.max, other.max),
- quantifier=self.quantifier + other.quantifier,
- )
-
- @property
- def mean(self):
- """
- The current mean value.
- """
- return self.get('mean')
-
- @property
- def min(self):
- """
- The current min value.
- """
- return self.get('min_val')
-
- @property
- def max(self):
- """
- The current max value.
- """
- return self.get('max_val')
-
- @property
- def quantifier(self):
- """
- The current quantifier (number of values for continues statistic).
- """
- return self.get('quantifier')
-
- def pop(self):
- """
- This pops out the current statistic data and returns these as an instance of :class:`helpers.continues_statistic`.
-
- :returns: The current statistic or None, if no value is available.
- :rtype: :class:`helpers.continues_statistic` or None
- """
- if self.quantifier == 0:
- return None
- else:
- rv = continues_statistic(self.mean, self.min, self.max, self.quantifier)
- self.__init_data__(None, None, None, 0)
- return rv
-
- def __str__(self):
- return "mean=%(mean)s, min=%(min_val)s, max=%(max_val)s, quantifier=%(quantifier)s" % self
-
-
- class continues_statistic_multivalue(dict):
- """
- This class stores multiple statistics of stream values. The statistic of each value is stored as :class:`helpers.continues_statistic`.
-
- .. note:: You can use the mathematic operators "+, +=" to add instances.
-
- .. note:: You are able to initialise this class in the following ways:
-
- * Without arguments to get an empty instance
- * With a keword argument(s) which will be passed to :class:`continues_statistic`
- * With a dict equivalent of :class:`continues_statistic_multivalue` to initialise an existing statistic
-
- **Example:**
-
- .. literalinclude:: helpers/_examples_/continues_statistic_multivalue.py
-
- Will result to the following output:
-
- .. literalinclude:: helpers/_examples_/continues_statistic_multivalue.log
- """
- def __init__(self, *args, **kwargs):
- dict.__init__(self)
- if len(args) == 0 and len(kwargs) >= 0:
- for key in kwargs:
- if type(kwargs[key]) is continues_statistic:
- self[key] = kwargs[key]
- elif type(kwargs[key]) is dict:
- self[key] = continues_statistic(**kwargs[key])
- else:
- self[key] = continues_statistic(kwargs[key])
- elif len(args) == 1 and len(kwargs) == 0:
- for key in args[0]:
- self[key] = continues_statistic(**args[0][key])
- else:
- raise TypeError("No valid initialisation value(s)!")
-
- def __add__(self, other):
- rv_dict = {}
- for key in set(list(self.keys()) + list(other.keys())):
- rv_dict[key] = self.get(key, continues_statistic()) + other.get(key, continues_statistic())
- return continues_statistic_multivalue(**rv_dict)
-
- def pop(self, key=None):
- """
- This pops out the current statistic data for a given key and returns these as an instance of :class:`helpers.continues_statistic`.
-
- If no key is given it pops out the current statistic data and returns these as an instance of :class:`helpers.continues_statistic_multiple`.
-
- :param key: The key to get data for
- :type key: str or NoneType
-
- :returns: The current statistic or None, if no value is available.
- :rtype: :class:`helpers.continues_statistic` or None
- """
- if key is None:
- if len(self) == 0:
- return None
- else:
- rv = continues_statistic_multivalue(**self)
- self.clear()
- return rv
- else:
- return self[key].pop()
-
- def __str__(self):
- if len(self) == 0:
- return '-'
- else:
- return '\n'.join([key + ': ' + str(self[key]) for key in self])
-
-
- class direct_socket_base(object):
- """
- This is the base class for other classes in this module.
- """
- DEFAULT_CHANNEL_NAME = 'all_others'
-
- def __init__(self, max_len=None, virtual_rate_bps=None):
- self.__max_length__ = max_len
- self.__rate_bps__ = virtual_rate_bps
- #
- self.init_channel_name()
- self.__queue__ = task.threaded_queue()
- self.__queue__.run()
- #
- self.__remote_socket__ = None
- self.__last_remote_socket__ = None
- self.__data_callback__ = None
- self.__connect_callback__ = None
- self.__disconnect_callback__ = None
- #
- self.__clean_buffer__()
-
- def __chunks__(self, data):
- chunks = []
- if self.__max_length__ is None:
- chunks.append(data)
- else:
- for i in range(0, len(data), self.__max_length__):
- chunks.append(data[i:i + self.__max_length__])
- return chunks
-
- def __clean_buffer__(self):
- self.__rx_buffer__ = b''
- self.logger.info('%s Cleaning RX-Buffer...', self.__log_prefix__())
-
- def __connect__(self, remote_socket):
- if self.__remote_socket__ is None:
- self.__remote_socket__ = remote_socket
- self.logger.info('%s Connection established...', self.__log_prefix__())
- self.__clean_buffer__()
- if self.__connect_callback__ is not None:
- self.__connect_callback__()
- remote_socket.__connect__(self)
-
- def __log_prefix__(self):
- return 'Client:' if self.IS_CLIENT else 'Server:'
-
- def __rx__(self, data):
- self.__rx_buffer__ += data
- self.logger.debug('%s RX <- %s', self.__log_prefix__(), stringtools.hexlify(data))
- if self.__data_callback__ is not None:
- self.__data_callback__(self)
-
- def __tx__(self, q, data):
- self.logger.debug('%s TX -> %s', self.__log_prefix__(), stringtools.hexlify(data))
- if self.__rate_bps__ is not None:
- time.sleep(len(data) / self.__rate_bps__)
- self.__remote_socket__.__rx__(data)
-
- def disconnect(self):
- """
- Method to disconnect client and server.
- """
- if self.__remote_socket__ is not None:
- self.__last_remote_socket__ = self.__remote_socket__
- self.__remote_socket__ = None
- self.logger.info('%s Connection Lost...', self.__log_prefix__())
- if self.__disconnect_callback__ is not None:
- self.__disconnect_callback__()
- self.__last_remote_socket__.disconnect()
-
- def init_channel_name(self, channel_name=None):
- """
- With this Method, the channel name for logging can be changed.
-
- :param channel_name: The name for the logging channel
- :type channel_name: str
- """
- if channel_name is None:
- self.logger = logger.getChild(self.DEFAULT_CHANNEL_NAME)
- else:
- self.logger = logger.getChild(channel_name)
-
- def is_connected(self):
- """
- With this Method the connection status can be identified.
-
- :return: True, if a connection is established, otherwise False.
- :rtype: bool
- """
- return self.__remote_socket__ is not None
-
- def receive(self, timeout=1.0, num=None):
- """
- This method returns received data.
-
- :param timeout: The timeout for receiving data (at least after the timeout the method returns data or None).
- :type timeout: float
- :param num: the number of bytes to receive (use None to get all available data).
- :type num: int or None
- :return: The received data.
- :rtype: bytes
- """
- i = 0
- while len(self.__rx_buffer__) < (num or 1) and i < timeout * 10:
- i += 1
- time.sleep(.1)
- if len(self.__rx_buffer__) < (num or 1):
- return b''
- else:
- if num is None:
- rv = self.__rx_buffer__
- self.__rx_buffer__ = b''
- else:
- rv = self.__rx_buffer__[:num]
- self.__rx_buffer__ = self.__rx_buffer__[num:]
- return rv
-
- def register_callback(self, callback):
- """
- This method stores the callback which is executed, if data is available. You need to execute :func:`receive` of this instance
- given as first argument.
-
- :param callback: The callback which will be executed, when data is available.
- :type callback:
- """
- self.__data_callback__ = callback
-
- def register_connect_callback(self, callback):
- """
- This method stores the callback which is executed, if a connection is established.
-
- :param callback: The callback which will be executed, when a connection is established.
- :type callback:
- """
- self.__connect_callback__ = callback
-
- def register_disconnect_callback(self, callback):
- """
- This method stores the callback which is executed, after the connection is lost.
-
- :param callback: The callback which will be executed, after the connection is lost.
- :type callback:
- """
- self.__disconnect_callback__ = callback
-
- def send(self, data, timeout=1.0):
- """
- This method sends data via the initiated communication channel.
-
- :param data: The data to be send over the communication channel.
- :type data: bytes
- :param timeout: The timeout for sending data (e.g. time to establish new connection).
- :type timeout: float
- :param log_lvl: The log level to log outgoing TX-data
- :type log_lvl: int
- :return: True if data had been sent, otherwise False.
- :rtype: bool
- """
- i = 0
- while not self.is_connected() and i < timeout * 10:
- i += 1
- time.sleep(.1)
- if not self.is_connected():
- return False
- else:
- for tx_data in self.__chunks__(data):
- self.__queue__.enqueue(1, self.__tx__, tx_data)
- return True
-
-
- class direct_socket_client(direct_socket_base):
- """
- Class to create a direct client socket. See also parent :class:`helpers.direct_socket_base`.
-
- **Example:**
-
- .. literalinclude:: helpers/_examples_/direct_socket.py
-
- Will result to the following output:
-
- .. literalinclude:: helpers/_examples_/direct_socket.log
- """
- IS_CLIENT = True
-
- def connect(self, remote_socket):
- """
- Method to create a connection between this client and a :class:`helpers.direct_socket_server` instance.
-
- :param remote_socket: The remote socket to connect to.
- :type remote_socket: :class:`helpers.direct_socket_server`
- """
- self.__connect__(remote_socket)
-
- def reconnect(self):
- """
- Method to do a reconnect.
-
- .. note:: The :const:`remote_socket` of the prefious :func:`connect` call will be used.
- """
- if self.__last_remote_socket__ is not None and self.__remote_socket__ is None:
- self.connect(self.__last_remote_socket__)
- return True
- return False
-
-
- class direct_socket_server(direct_socket_base):
- """
- Class to create a direct server socket. See also parent :class:`helpers.direct_socket_base`.
-
- **Example:**
-
- .. literalinclude:: helpers/_examples_/direct_socket.py
-
- Will result to the following output:
-
- .. literalinclude:: helpers/_examples_/direct_socket.log
- """
- IS_CLIENT = False
-
- def __init__(self, *args, **kwargs):
- direct_socket_base.__init__(self, *args, **kwargs)
- self.logger.info('%s Waiting for incomming connection', self.__log_prefix__())
-
-
- class ringbuffer(list):
- """
- Class for a list with a limited number of elements.
-
- .. note:: On adding Objects, the list will be reduced to the maximum length by deleting entries starting from index 0.
-
- **Example:**
-
- .. literalinclude:: helpers/_examples_/ringbuffer.py
-
- Will result to the following output:
-
- .. literalinclude:: helpers/_examples_/ringbuffer.log
- """
- def __init__(self, *args, **kwargs):
- self.__max_length__ = kwargs.pop('length')
- list.__init__(self, *args, **kwargs)
- self.__reduce_list__()
-
- def __reduce_list__(self):
- while len(self) > self.__max_length__:
- self.pop(0)
-
- def append(self, obj):
- rv = list.append(self, obj)
- self.__reduce_list__()
- return rv
-
- def extend(self, iterable):
- rv = list.extend(self, iterable)
- self.__reduce_list__()
- return rv
|