async def send_message(self, message: Union[str, int, float], connection: Union[Connection, Iterable[Connection]] = None, blocking: bool = False) -> bool: ''' Send a message to either one or more connections. This function by default is a fire & forget method, but when set to `blocking=True` waits if the message could be dispatched to all recipients. Only the latter returns a real (boolean) result, telling if the message could be successfully written to (not received by) the client(s) ''' try: # Get affected connections affected = [connection ] if not checker.isIterable(connection) else connection connections = [c for c in affected if not c.state['closed']] # Encode and check message message = str(message) if self.lines and not message.endswith(self.line_separator): message += self.line_separator message = message.encode() if len(message) == 0: return False if self.limit and len(message) > self.limit: await self.handleLimitExceedance(connection, message, False) return False # Dispatch message to affected connections as long as we're not shutting down if len(connections) > 0 and not self._shutdown: writer = asyncio.gather( *[self._dispatchMessage(message, c) for c in connections], loop=self.loop, return_exceptions=True) # Adding as many callbacks as connections, so the future (writer) gets removed from all connections def cancel_writer(connection, writer): try: connection.write_handlers.remove(writer) except: pass for c in connections: # We use functools to properly bind the callback to the connection writer.add_done_callback( functools.partial(cancel_writer, c)) c.write_handlers.add(writer) # Return behavior if not blocking: return True else: await writer return all(r for r in writer.result()) else: return False except: return False
def connected(self): '''Shows if a connection is established''' if checker.isExactType(self.__connection, 'rpyc.core.protocol.Connection'): if hasattr(self.__connection, '_closed'): if self.__connection._channel: return self.__connection._channel.stream.closed is not True else: return False else: return False else: return False
def run_concurrently(function: Callable, tasks: Iterable[Any], *args, max_threads: int = None, timeout: int = None) -> None: ''' This method runs a given non-coroutine function concurrently for each task item by the help of the asyncio module and a concurrent executor. The executor creates a thread for each task item if no max_threads is set and cleans up its resources afterwards. :param func function: The function to run :param Iterable tasks: A list/tuple of items for each of them we run the function. The items will also serve as argument to the function. Each further *args parameter will be used for the function :param int max_threads: Limit the number of threads. By default we create a thread for each item :param int timeout: Limit the execution time by a timeout ''' # Check requirements: A function and a non-empty list if checker.is_function(function) and checker.is_iterable( tasks) and len(tasks) > 0: # First get this threads loop or create a new one try: loop = asyncio.get_event_loop() except RuntimeError: loop = asyncio.new_event_loop() asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) asyncio.set_event_loop(loop) # Create a worker pool for each "task" in the list (which are to be executed parallel workers: List[Coroutine[ThreadPoolExecutor, Callable, Tuple]] = [] with ThreadPoolExecutor(max_workers=max_threads if checker.is_integer( max_threads) else len(tasks)) as executor: # Create coroutine threads for t in tasks: workers.append( loop.run_in_executor(executor, function, *tuple([t] + list(args)))) # Run all release operations in parallel if workers: loop.run_until_complete(asyncio.wait(workers, timeout=timeout))
def exposed_printMessage(self, message): ''' Prints a message sent to the stout of the client :param str message: The message :raises ValueError: If the message exceeds 160 characters :raises TypeError: If the message is not a basestring ''' max_length=160 if checker.isString(message): if(len(message) <= max_length): print(message) else: raise ValueError(f'Message exceeds {max_length} characters') else: raise TypeError('Not a string')
def testAlphaStrings(self): tests = [ ['Hello', True], ['Hello World', False], [b'Hello', True], ['Mažasančiųplūduriuojantisežero', True], ['小鴨子湖面上漂浮著', True], ['1小鴨子湖面上漂浮著', False], ['user-name', False], [1, False], [1.2, False], [{}, False], ] for t in tests: logger.debug( f'String "{t[0]}" is{" " if t[1] else " not "}alphabetical') if t[1]: self.assertTrue( checker.isAlpha(t[0]), f'Testing alphanumeric strings failed for "{t[0]}"') else: self.assertFalse( checker.isAlpha(t[0]), f'Testing alphanumeric strings failed for "{t[0]}"')
def registerStore(self, store: Union[Dict[str, Any], Type[DataStore]]) -> bool: ''' Registers a data store to the manager :param [dict/py:class::freedm.data.objects.DataStore] store: The data store or a configuration dictionary :returns: ``True`` if the store could be registered :rtype: bool ''' # Check store if (checker.isDict(store)): try: store = DataStore(**store) except TypeError as e: self.logger.warn( f'Cannot create data store instance from invalid parameters ({e})' ) elif not isinstance(store, DataStore): self.logger.warn( f'Cannot register invalid store class "{store}" in data manager "{self}"' ) # Add store if not store.alias in self.__stores.keys(): # Add the store and set the store's path if empty self.__stores[store.alias] = store if store.path is None: store.path = self.path # Warn the user from registering same-type stores with the same path if len(self.__stores) >= 2: self.logger.warn( f'Registered store uses "{self.path}". Beware of registering same-type stores with an equal path' ) # Update setters & getters self.__updateSettersGetters() self.logger.debug( f'Registered data store "{store}" in data manager "{self}"') return True else: self.logger.warn( f'Cannot register a store with name "{store.alias}" twice in data manager "{self}"' ) # If store could not be registered return False
def exposed_getDaemonInfo(self): ''' Return the most important information regarding the daemon instance, thus as the class, role, state, etc. :return: The daemon instance data :rtype: dict ''' if self.daemon: return dict( type = checker.getExactType(self.daemon), role = self.daemon.role.capitalize(), state = self.daemon.state.capitalize(), pid = self.daemon.pid, version = self.daemon.version, address = self.daemon.host if not '127.0.0.1' else 'localhost', port = self.daemon.port ) else: return {}
def __init__(self, loop: Optional[Type[asyncio.AbstractEventLoop]] = None, limit: Optional[int] = None, chunksize: Optional[int] = None, max_connections: Optional[int] = None, mode: Optional[ConnectionType] = None, protocol: Optional[Protocol] = None) -> None: self.logger = logging.getLogger() self.loop = loop or getLoop() self.limit = limit self.chunksize = chunksize self.mode = mode self.protocol = protocol if not self.name: self.name = self.__class__.__name__ if not self._connection_pool: self._connection_pool = ConnectionPool() if checker.isInteger(max_connections): self._connection_pool.max = max_connections
def __updateSettersGetters(self) -> None: ''' Creates or removes getter/setter methods on the data manager for each registered store ''' # First remove any obsolete getters & setters ... attrs = vars(self) obsolete = [] for v in attrs.keys(): if (v.startswith('get') or v.startswith('set')) and checker.isFunction(attrs[v]): try: self.__stores[v[3:]] except: obsolete.append(v) for o in obsolete: delattr(self, o) # ... then add getters & setters for all current stores created = [] for alias in self.__stores: # Create Getter & Setter if not hasattr(self, f'get{alias}') and not hasattr( self, f'set{alias}'): setattr(self, f'get{alias}', lambda token, default=None, store=alias: self. __getStoreValue(token, default, store)) setattr(self, f'set{alias}', lambda token, value=None, store=alias: self. __setStoreValue(token, value, store)) created.append(alias) # Log is we created setter/getter for c in created: self.logger.debug( f'Added data getters & setters for store "{self.__stores[c]}" (Use "get{c}" & "set{c}" for access)' )
def __init__(self, path: Union[str, Path] = None): try: # Set the data location if path is not None: # Make sure we work with an absolute path path = Path(path).resolve() if checker.isString(path) else path # Check path availability if path is False: pass elif os.path.exists(path) and os.path.isdir( path) and os.access(path, os.W_OK): self.__path = path else: self.logger.critical( f'Cannot access provided storage path "{path}"') else: if not os.path.exists(self.__path) or not os.path.isdir( self.__path) or not os.access(self.__path, os.W_OK): self.logger.critical( f'Cannot access default storage path "{self.__path}"') except Exception as e: self.logger.warn(f'Failed to initialize DataManager ({e})') self.logger.debug(f'Using data storage location "{self.__path}"')
def logger(self, logger): if checker.isExactType(logger, 'logging.Logger') or checker.isExactType( logger, 'logging.RootLogger'): self.__logger = logger
def _loadDomain(self, domain: str, path: str) -> None: try: # Check that we have a FS path for the INI file if not path: raise UserWarning(f'No INI file location provided!') # The backend file inifile = path.joinpath(f'{domain}.{self.filetype}') # Read the data from file & convert values parser = SafeConfigParser() parser.read(inifile) inidata = {} for s in parser.sections(): items = [] # Handle each item for item in parser.items(s): # Try to convert item values automatically try: k, v = item if v is not None: # Get integers from config file if v.isdigit(): v = parser.getint(s, k) # Get floats from config file elif checker.isFloat(v): v = parser.getfloat(s, k) # Get booleans from config file elif v in ('1', 'yes', 'true', 'on', '0', 'no', 'false', 'off'): v = parser.getboolean(s, k) # Get nested JSON objects from config file elif v.startswith('{') and v.endswith('}'): v = json.loads(v) # Get nested JSON objects from config file elif v.startswith('[') and v.endswith(']'): v = json.loads(v) # Get wrapped values as string elif v[0] in ('"', '\'') and v[-1] in ('"', "'"): v = v[1:-1] items.append((k, v)) except Exception as e: print(e.__class__.__name__) if e.__class__.__name__ in ('json.JSONDecodeError', 'ValueError'): raise UserWarning( f'File "{inifile}" cannot be JSON-decoded ({e})!' ) else: items.append(item) # Re-add all items & sections inidata.update({s: dict(items)}) # Create data object if isinstance(inidata, dict): data = DataObject(backend=inifile, **inidata) return data except FileNotFoundError: raise UserWarning( f'File "{inifile}" does not exist. Please create this file!') except Exception as e: raise e