def test_console_output(self, capsys, monkeypatch, printer): monkeypatch.setitem(os.environ, 'PYPE_DEBUG', '3') lf = Logger() assert lf.PYPE_DEBUG == 3 logger = Logger().get_logger('test_output', 'tests') printer("DEBUG LEVEL SET: {}".format(os.environ.get('PYPE_DEBUG'))) # critical printer("testing critical level") logger.critical("CRITICAL TEST") cap = capsys.readouterr() cri_regex = re.compile( r'\x1b\[1m\x1b\[31m!!! CRI: \x1b\[0m.* \x1b\[1m\x1b\[32m>>> \x1b\[0m\x1b\[92m{ test_output }\x1b\[0m: \x1b\[1m\x1b\[92m\[ \x1b\[0mCRITICAL TEST \x1b\[1m\x1b\[92m]\x1b\[0m \x1b\[0m\n' ) # noqa: E501 assert cri_regex.match(cap[1]) # error printer("testing error level") logger.error("ERROR TEST") cap = capsys.readouterr() err_regex = re.compile( r'\x1b\[1m\x1b\[91m!!! ERR: \x1b\[0m.* \x1b\[1m\x1b\[32m>>> \x1b\[0m\x1b\[92m{ test_output }\x1b\[0m: \x1b\[1m\x1b\[92m\[ \x1b\[0m\x1b\[1m\x1b\[91mERROR\x1b\[0m TEST \x1b\[1m\x1b\[92m]\x1b\[0m \x1b\[0m\n' ) # noqa: E501 assert err_regex.match(cap[1]) # warn printer("testing warning level") logger.warning("WARNING TEST") cap = capsys.readouterr() warn_regex = re.compile( r'\x1b\[1m\x1b\[93m\*\*\* WRN\x1b\[0m: \x1b\[1m\x1b\[32m>>> \x1b\[0m\x1b\[92m{ test_output }\x1b\[0m: \x1b\[1m\x1b\[92m\[ \x1b\[0mWARNING TEST \x1b\[1m\x1b\[92m]\x1b\[0m \x1b\[0m\n' ) # noqa: E501 assert warn_regex.match(cap[1]) # info printer("testing info level") logger.info("INFO TEST") cap = capsys.readouterr() info_regex = re.compile( r'\x1b\[1m\x1b\[32m>>> \x1b\[0m\x1b\[1m\x1b\[92m\[ \x1b\[0mINFO TEST \x1b\[1m\x1b\[92m]\x1b\[0m \x1b\[0m\n' ) # noqa: E501 assert info_regex.match(cap[1]) # debug printer("testing debug level") logger.debug("DEBUG TEST") cap = capsys.readouterr() debug_regex = re.compile( r'\x1b\[1m\x1b\[33m - \x1b\[0m\x1b\[92m{ test_output }\x1b\[0m: \x1b\[1m\x1b\[92m\[ \x1b\[0mDEBUG TEST \x1b\[1m\x1b\[92m]\x1b\[0m \x1b\[0m\n' ) # noqa: E501 assert debug_regex.match(cap[1])
class SocketThread(threading.Thread): """Thread that checks suprocess of storer of processor of events""" MAX_TIMEOUT = 35 def __init__(self, name, port, filepath, additional_args=[]): super(SocketThread, self).__init__() self.log = Logger().get_logger(self.__class__.__name__) self.setName(name) self.name = name self.port = port self.filepath = filepath self.additional_args = additional_args self.sock = None self.subproc = None self.connection = None self._is_running = False self.finished = False self.mongo_error = False self._temp_data = {} def stop(self): self._is_running = False def run(self): self._is_running = True time_socket = time.time() # Create a TCP/IP socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock = sock # Bind the socket to the port - skip already used ports while True: try: server_address = ("localhost", self.port) sock.bind(server_address) break except OSError: self.port += 1 self.log.debug( "Running Socked thread on {}:{}".format(*server_address)) self.subproc = subprocess.Popen([ sys.executable, self.filepath, *self.additional_args, str(self.port) ], stdin=subprocess.PIPE) # Listen for incoming connections sock.listen(1) sock.settimeout(1.0) while True: if not self._is_running: break try: connection, client_address = sock.accept() time_socket = time.time() connection.settimeout(1.0) self.connection = connection except socket.timeout: if (time.time() - time_socket) > self.MAX_TIMEOUT: self.log.error("Connection timeout passed. Terminating.") self._is_running = False self.subproc.terminate() break continue try: time_con = time.time() # Receive the data in small chunks and retransmit it while True: try: if not self._is_running: break data = None try: data = self.get_data_from_con(connection) time_con = time.time() except socket.timeout: if (time.time() - time_con) > self.MAX_TIMEOUT: self.log.error( "Connection timeout passed. Terminating.") self._is_running = False self.subproc.terminate() break continue except ConnectionResetError: self._is_running = False break self._handle_data(connection, data) except Exception as exc: self.log.error("Event server process failed", exc_info=True) finally: # Clean up the connection connection.close() if self.subproc.poll() is None: self.subproc.terminate() self.finished = True def get_data_from_con(self, connection): return connection.recv(16) def _handle_data(self, connection, data): if not data: return if data == b"MongoError": self.mongo_error = True connection.sendall(data)
class TrayManager: """Cares about context of application. Load submenus, actions, separators and modules into tray's context. """ modules = {} services = {} services_submenu = None errors = [] items = get_presets(first_run=True).get('tray', {}).get('menu_items', []) available_sourcetypes = ['python', 'file'] def __init__(self, tray_widget, main_window): self.tray_widget = tray_widget self.main_window = main_window self.log = Logger().get_logger(self.__class__.__name__) self.icon_run = QtGui.QIcon(get_resource('circle_green.png')) self.icon_stay = QtGui.QIcon(get_resource('circle_orange.png')) self.icon_failed = QtGui.QIcon(get_resource('circle_red.png')) self.services_thread = None def process_presets(self): """Add modules to tray by presets. This is start up method for TrayManager. Loads presets and import modules described in "menu_items.json". In `item_usage` key you can specify by item's title or import path if you want to import it. Example of "menu_items.json" file: { "item_usage": { "Statics Server": false } }, { "item_import": [{ "title": "Ftrack", "type": "module", "import_path": "pype.ftrack.tray", "fromlist": ["pype", "ftrack"] }, { "title": "Statics Server", "type": "module", "import_path": "pype.services.statics_server", "fromlist": ["pype","services"] }] } In this case `Statics Server` won't be used. """ # Backwards compatible presets loading if isinstance(self.items, list): items = self.items else: items = [] # Get booleans is module should be used usages = self.items.get("item_usage") or {} for item in self.items.get("item_import", []): import_path = item.get("import_path") title = item.get("title") item_usage = usages.get(title) if item_usage is None: item_usage = usages.get(import_path, True) if item_usage: items.append(item) else: if not title: title = import_path self.log.debug("{} - Module ignored".format(title)) if items: self.process_items(items, self.tray_widget.menu) # Add services if they are if self.services_submenu is not None: self.tray_widget.menu.addMenu(self.services_submenu) # Add separator if items and self.services_submenu is not None: self.add_separator(self.tray_widget.menu) # Add Exit action to menu aExit = QtWidgets.QAction("&Exit", self.tray_widget) aExit.triggered.connect(self.tray_widget.exit) self.tray_widget.menu.addAction(aExit) # Tell each module which modules were imported self.connect_modules() self.start_modules() def process_items(self, items, parent_menu): """ Loop through items and add them to parent_menu. :param items: contains dictionary objects representing each item :type items: list :param parent_menu: menu where items will be add :type parent_menu: QtWidgets.QMenu """ for item in items: i_type = item.get('type', None) result = False if i_type is None: continue elif i_type == 'module': result = self.add_module(item, parent_menu) elif i_type == 'action': result = self.add_action(item, parent_menu) elif i_type == 'menu': result = self.add_menu(item, parent_menu) elif i_type == 'separator': result = self.add_separator(parent_menu) if result is False: self.errors.append(item) def add_module(self, item, parent_menu): """Inicialize object of module and add it to context. :param item: item from presets containing information about module :type item: dict :param parent_menu: menu where module's submenus/actions will be add :type parent_menu: QtWidgets.QMenu :returns: success of module implementation :rtype: bool REQUIRED KEYS (item): :import_path (*str*): - full import path as python's import - e.g. *"path.to.module"* :fromlist (*list*): - subparts of import_path (as from is used) - e.g. *["path", "to"]* OPTIONAL KEYS (item): :title (*str*): - represents label shown in services menu - import_path is used if title is not set - title is not used at all if module is not a service .. note:: Module is added as **service** if object does not have *tray_menu* method. """ import_path = item.get('import_path', None) title = item.get('title', import_path) fromlist = item.get('fromlist', []) try: module = __import__("{}".format(import_path), fromlist=fromlist) obj = module.tray_init(self.tray_widget, self.main_window) name = obj.__class__.__name__ if hasattr(obj, 'tray_menu'): obj.tray_menu(parent_menu) else: if self.services_submenu is None: self.services_submenu = QtWidgets.QMenu( 'Services', self.tray_widget.menu) action = QtWidgets.QAction(title, self.services_submenu) action.setIcon(self.icon_run) self.services_submenu.addAction(action) if hasattr(obj, 'set_qaction'): obj.set_qaction(action, self.icon_failed) self.modules[name] = obj self.log.info("{} - Module imported".format(title)) except ImportError as ie: if self.services_submenu is None: self.services_submenu = QtWidgets.QMenu( 'Services', self.tray_widget.menu) action = QtWidgets.QAction(title, self.services_submenu) action.setIcon(self.icon_failed) self.services_submenu.addAction(action) self.log.warning("{} - Module import Error: {}".format( title, str(ie)), exc_info=True) return False return True def add_action(self, item, parent_menu): """Adds action to parent_menu. :param item: item from presets containing information about action :type item: dictionary :param parent_menu: menu where action will be added :type parent_menu: QtWidgets.QMenu :returns: success of adding item to parent_menu :rtype: bool REQUIRED KEYS (item): :title (*str*): - represents label shown in menu :sourcetype (*str*): - type of action *enum["file", "python"]* :command (*str*): - filepath to script *(sourcetype=="file")* - python code as string *(sourcetype=="python")* OPTIONAL KEYS (item): :tooltip (*str*): - will be shown when hover over action """ sourcetype = item.get('sourcetype', None) command = item.get('command', None) title = item.get('title', '*ERROR*') tooltip = item.get('tooltip', None) if sourcetype not in self.available_sourcetypes: self.log.error('item "{}" has invalid sourcetype'.format(title)) return False if command is None or command.strip() == '': self.log.error('item "{}" has invalid command'.format(title)) return False new_action = QtWidgets.QAction(title, parent_menu) if tooltip is not None and tooltip.strip() != '': new_action.setToolTip(tooltip) if sourcetype == 'python': new_action.triggered.connect(lambda: exec(command)) elif sourcetype == 'file': command = os.path.normpath(command) if '$' in command: command_items = command.split(os.path.sep) for i in range(len(command_items)): if command_items[i].startswith('$'): # TODO: raise error if environment was not found? command_items[i] = os.environ.get( command_items[i].replace('$', ''), command_items[i]) command = os.path.sep.join(command_items) new_action.triggered.connect( lambda: exec(open(command).read(), globals())) parent_menu.addAction(new_action) def add_menu(self, item, parent_menu): """ Adds submenu to parent_menu. :param item: item from presets containing information about menu :type item: dictionary :param parent_menu: menu where submenu will be added :type parent_menu: QtWidgets.QMenu :returns: success of adding item to parent_menu :rtype: bool REQUIRED KEYS (item): :title (*str*): - represents label shown in menu :items (*list*): - list of submenus / actions / separators / modules *(dict)* """ try: title = item.get('title', None) if title is None or title.strip() == '': self.log.error('Missing title in menu from presets') return False new_menu = QtWidgets.QMenu(title, parent_menu) new_menu.setProperty('submenu', 'on') parent_menu.addMenu(new_menu) self.process_items(item.get('items', []), new_menu) return True except Exception: return False def add_separator(self, parent_menu): """ Adds separator to parent_menu. :param parent_menu: menu where submenu will be added :type parent_menu: QtWidgets.QMenu :returns: success of adding item to parent_menu :rtype: bool """ try: parent_menu.addSeparator() return True except Exception: return False def connect_modules(self): """Sends all imported modules to imported modules which have process_modules method. """ for obj in self.modules.values(): if hasattr(obj, 'process_modules'): obj.process_modules(self.modules) def start_modules(self): """Modules which can be modified by another modules and must be launched after *connect_modules* should have tray_start to start their process afterwards. (e.g. Ftrack actions) """ for obj in self.modules.values(): if hasattr(obj, 'tray_start'): obj.tray_start() def on_exit(self): for obj in self.modules.values(): if hasattr(obj, 'tray_exit'): try: obj.tray_exit() except Exception: self.log.error("Failed to exit module {}".format( obj.__class__.__name__))
class BaseHandler(object): '''Custom Action base class <label> - a descriptive string identifing your action. <varaint> - To group actions together, give them the same label and specify a unique variant per action. <identifier> - a unique identifier for app. <description> - a verbose descriptive text for you action <icon> - icon in ftrack ''' # Default priority is 100 priority = 100 # Type is just for logging purpose (e.g.: Action, Event, Application,...) type = 'No-type' ignore_me = False preactions = [] def __init__(self, session, plugins_presets={}): '''Expects a ftrack_api.Session instance''' self.log = Logger().get_logger(self.__class__.__name__) if not( isinstance(session, ftrack_api.session.Session) or isinstance(session, SocketSession) ): raise Exception(( "Session object entered with args is instance of \"{}\"" " but expected instances are \"{}\" and \"{}\"" ).format( str(type(session)), str(ftrack_api.session.Session), str(SocketSession) )) self._session = session # Using decorator self.register = self.register_decorator(self.register) self.launch = self.launch_log(self.launch) self.plugins_presets = plugins_presets # Decorator def register_decorator(self, func): @functools.wraps(func) def wrapper_register(*args, **kwargs): presets_data = self.plugins_presets.get(self.__class__.__name__) if presets_data: for key, value in presets_data.items(): if not hasattr(self, key): continue setattr(self, key, value) if self.ignore_me: return label = self.__class__.__name__ if hasattr(self, 'label'): if self.variant is None: label = self.label else: label = '{} {}'.format(self.label, self.variant) try: self._preregister() start_time = time.perf_counter() func(*args, **kwargs) end_time = time.perf_counter() run_time = end_time - start_time self.log.info(( '{} "{}" - Registered successfully ({:.4f}sec)' ).format(self.type, label, run_time)) except MissingPermision as MPE: self.log.info(( '!{} "{}" - You\'re missing required {} permissions' ).format(self.type, label, str(MPE))) except AssertionError as ae: self.log.warning(( '!{} "{}" - {}' ).format(self.type, label, str(ae))) except NotImplementedError: self.log.error(( '{} "{}" - Register method is not implemented' ).format(self.type, label)) except PreregisterException as exc: self.log.warning(( '{} "{}" - {}' ).format(self.type, label, str(exc))) except Exception as e: self.log.error('{} "{}" - Registration failed ({})'.format( self.type, label, str(e)) ) return wrapper_register # Decorator def launch_log(self, func): @functools.wraps(func) def wrapper_launch(*args, **kwargs): label = self.__class__.__name__ if hasattr(self, 'label'): label = self.label if hasattr(self, 'variant'): if self.variant is not None: label = '{} {}'.format(self.label, self.variant) self.log.info(('{} "{}": Launched').format(self.type, label)) try: return func(*args, **kwargs) except Exception as exc: self.session.rollback() msg = '{} "{}": Failed ({})'.format(self.type, label, str(exc)) self.log.error(msg, exc_info=True) return { 'success': False, 'message': msg } finally: self.log.info(('{} "{}": Finished').format(self.type, label)) return wrapper_launch @property def session(self): '''Return current session.''' return self._session def reset_session(self): self.session.reset() def _preregister(self): if hasattr(self, "role_list") and len(self.role_list) > 0: username = self.session.api_user user = self.session.query( 'User where username is "{}"'.format(username) ).one() available = False lowercase_rolelist = [x.lower() for x in self.role_list] for role in user['user_security_roles']: if role['security_role']['name'].lower() in lowercase_rolelist: available = True break if available is False: raise MissingPermision # Custom validations result = self.preregister() if result is None: self.log.debug(( "\"{}\" 'preregister' method returned 'None'. Expected it" " didn't fail and continue as preregister returned True." ).format(self.__class__.__name__)) return if result is True: return msg = None if isinstance(result, str): msg = result raise PreregisterException(msg) def preregister(self): ''' Preregister conditions. Registration continues if returns True. ''' return True def register(self): ''' Registers the action, subscribing the discover and launch topics. Is decorated by register_log ''' raise NotImplementedError() def _translate_event(self, event, session=None): '''Return *event* translated structure to be used with the API.''' if session is None: session = self.session _entities = event['data'].get('entities_object', None) if ( _entities is None or _entities[0].get( 'link', None ) == ftrack_api.symbol.NOT_SET ): _entities = self._get_entities(event) event['data']['entities_object'] = _entities return _entities def _get_entities(self, event, session=None, ignore=None): entities = [] selection = event['data'].get('selection') if not selection: return entities if ignore is None: ignore = [] elif isinstance(ignore, str): ignore = [ignore] filtered_selection = [] for entity in selection: if entity['entityType'] not in ignore: filtered_selection.append(entity) if not filtered_selection: return entities if session is None: session = self.session session._local_cache.clear() for entity in filtered_selection: entities.append(session.get( self._get_entity_type(entity, session), entity.get('entityId') )) return entities def _get_entity_type(self, entity, session=None): '''Return translated entity type tht can be used with API.''' # Get entity type and make sure it is lower cased. Most places except # the component tab in the Sidebar will use lower case notation. entity_type = entity.get('entityType').replace('_', '').lower() if session is None: session = self.session for schema in self.session.schemas: alias_for = schema.get('alias_for') if ( alias_for and isinstance(alias_for, str) and alias_for.lower() == entity_type ): return schema['id'] for schema in self.session.schemas: if schema['id'].lower() == entity_type: return schema['id'] raise ValueError( 'Unable to translate entity type: {0}.'.format(entity_type) ) def _launch(self, event): self.session.rollback() self.session._local_cache.clear() self.launch(self.session, event) def launch(self, session, event): '''Callback method for the custom action. return either a bool ( True if successful or False if the action failed ) or a dictionary with they keys `message` and `success`, the message should be a string and will be displayed as feedback to the user, success should be a bool, True if successful or False if the action failed. *session* is a `ftrack_api.Session` instance *entities* is a list of tuples each containing the entity type and the entity id. If the entity is a hierarchical you will always get the entity type TypedContext, once retrieved through a get operation you will have the "real" entity type ie. example Shot, Sequence or Asset Build. *event* the unmodified original event ''' raise NotImplementedError() def _handle_preactions(self, session, event): # If preactions are not set if len(self.preactions) == 0: return True # If no selection selection = event.get('data', {}).get('selection', None) if (selection is None): return False # If preactions were already started if event['data'].get('preactions_launched', None) is True: return True # Launch preactions for preaction in self.preactions: self.trigger_action(preaction, event) # Relaunch this action additional_data = {"preactions_launched": True} self.trigger_action( self.identifier, event, additional_event_data=additional_data ) return False def _handle_result(self, result): '''Validate the returned result from the action callback''' if isinstance(result, bool): if result is True: result = { 'success': result, 'message': ( '{0} launched successfully.'.format(self.label) ) } else: result = { 'success': result, 'message': ( '{0} launch failed.'.format(self.label) ) } elif isinstance(result, dict): items = 'items' in result if items is False: for key in ('success', 'message'): if key in result: continue raise KeyError( 'Missing required key: {0}.'.format(key) ) return result def show_message(self, event, input_message, result=False): """ Shows message to user who triggered event - event - just source of user id - input_message - message that is shown to user - result - changes color of message (based on ftrack settings) - True = Violet - False = Red """ if not isinstance(result, bool): result = False try: message = str(input_message) except Exception: return user_id = event['source']['user']['id'] target = ( 'applicationId=ftrack.client.web and user.id="{0}"' ).format(user_id) self.session.event_hub.publish( ftrack_api.event.base.Event( topic='ftrack.action.trigger-user-interface', data=dict( type='message', success=result, message=message ), target=target ), on_error='ignore' ) def show_interface( self, items, title='', event=None, user=None, username=None, user_id=None ): """ Shows interface to user - to identify user must be entered one of args: event, user, username, user_id - 'items' must be list containing Ftrack interface items """ if not any([event, user, username, user_id]): raise TypeError(( 'Missing argument `show_interface` requires one of args:' ' event (ftrack_api Event object),' ' user (ftrack_api User object)' ' username (string) or user_id (string)' )) if event: user_id = event['source']['user']['id'] elif user: user_id = user['id'] else: if user_id: key = 'id' value = user_id else: key = 'username' value = username user = self.session.query( 'User where {} is "{}"'.format(key, value) ).first() if not user: raise TypeError(( 'Ftrack user with {} "{}" was not found!' ).format(key, value)) user_id = user['id'] target = ( 'applicationId=ftrack.client.web and user.id="{0}"' ).format(user_id) self.session.event_hub.publish( ftrack_api.event.base.Event( topic='ftrack.action.trigger-user-interface', data=dict( type='widget', items=items, title=title ), target=target ), on_error='ignore' ) def show_interface_from_dict( self, messages, title="", event=None, user=None, username=None, user_id=None ): if not messages: self.log.debug("No messages to show! (messages dict is empty)") return items = [] splitter = {'type': 'label', 'value': '---'} first = True for key, value in messages.items(): if not first: items.append(splitter) else: first = False subtitle = {'type': 'label', 'value': '<h3>{}</h3>'.format(key)} items.append(subtitle) if isinstance(value, list): for item in value: message = { 'type': 'label', 'value': '<p>{}</p>'.format(item) } items.append(message) else: message = {'type': 'label', 'value': '<p>{}</p>'.format(value)} items.append(message) self.show_interface(items, title, event, user, username, user_id) def trigger_action( self, action_name, event=None, session=None, selection=None, user_data=None, topic="ftrack.action.launch", additional_event_data={}, on_error="ignore" ): self.log.debug("Triggering action \"{}\" Begins".format(action_name)) if not session: session = self.session # Getting selection and user data _selection = None _user_data = None if event: _selection = event.get("data", {}).get("selection") _user_data = event.get("source", {}).get("user") if selection is not None: _selection = selection if user_data is not None: _user_data = user_data # Without selection and user data skip triggering msg = "Can't trigger \"{}\" action without {}." if _selection is None: self.log.error(msg.format(action_name, "selection")) return if _user_data is None: self.log.error(msg.format(action_name, "user data")) return _event_data = { "actionIdentifier": action_name, "selection": _selection } # Add additional data if additional_event_data: _event_data.update(additional_event_data) # Create and trigger event session.event_hub.publish( ftrack_api.event.base.Event( topic=topic, data=_event_data, source=dict(user=_user_data) ), on_error=on_error ) self.log.debug( "Action \"{}\" Triggered successfully".format(action_name) ) def trigger_event( self, topic, event_data={}, session=None, source=None, event=None, on_error="ignore" ): if session is None: session = self.session if not source and event: source = event.get("source") # Create and trigger event event = ftrack_api.event.base.Event( topic=topic, data=event_data, source=source ) session.event_hub.publish(event, on_error=on_error) self.log.debug(( "Publishing event: {}" ).format(str(event.__dict__))) def get_project_from_entity(self, entity): low_entity_type = entity.entity_type.lower() if low_entity_type == "project": return entity if "project" in entity: # reviewsession, task(Task, Shot, Sequence,...) return entity["project"] if low_entity_type == "filecomponent": entity = entity["version"] low_entity_type = entity.entity_type.lower() if low_entity_type == "assetversion": asset = entity["asset"] if asset: parent = asset["parent"] if parent: return parent["project"] project_data = entity["link"][0] return self.session.query( "Project where id is {}".format(project_data["id"]) ).one()
class SocketThread(threading.Thread): """Thread that checks suprocess of storer of processor of events""" MAX_TIMEOUT = 35 def __init__(self, name, port, filepath): super(SocketThread, self).__init__() self.log = Logger().get_logger("SocketThread", "Event Thread") self.setName(name) self.name = name self.port = port self.filepath = filepath self.sock = None self.subproc = None self.connection = None self._is_running = False self.finished = False self.mongo_error = False def stop(self): self._is_running = False def run(self): self._is_running = True time_socket = time.time() # Create a TCP/IP socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock = sock # Bind the socket to the port - skip already used ports while True: try: server_address = ("localhost", self.port) sock.bind(server_address) break except OSError: self.port += 1 self.log.debug( "Running Socked thread on {}:{}".format(*server_address)) self.subproc = subprocess.Popen( ["python", self.filepath, "-port", str(self.port)], stdout=subprocess.PIPE) # Listen for incoming connections sock.listen(1) sock.settimeout(1.0) while True: if not self._is_running: break try: connection, client_address = sock.accept() time_socket = time.time() connection.settimeout(1.0) self.connection = connection except socket.timeout: if (time.time() - time_socket) > self.MAX_TIMEOUT: self.log.error("Connection timeout passed. Terminating.") self._is_running = False self.subproc.terminate() break continue try: time_con = time.time() # Receive the data in small chunks and retransmit it while True: try: if not self._is_running: break try: data = connection.recv(16) time_con = time.time() except socket.timeout: if (time.time() - time_con) > self.MAX_TIMEOUT: self.log.error( "Connection timeout passed. Terminating.") self._is_running = False self.subproc.terminate() break continue except ConnectionResetError: self._is_running = False break if data: if data == b"MongoError": self.mongo_error = True connection.sendall(data) except Exception as exc: self.log.error("Event server process failed", exc_info=True) finally: # Clean up the connection connection.close() if self.subproc.poll() is None: self.subproc.terminate() lines = self.subproc.stdout.readlines() if lines: print("*** Socked Thread stdout ***") for line in lines: os.write(1, line) self.finished = True