def __init__(self, session): '''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, ftrack_server.lib.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(ftrack_server.lib.SocketSession))) self._session = session # Using decorator self.register = self.register_decorator(self.register) self.launch = self.launch_log(self.launch)
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 __init__(self, tray_widget, main_window): self.tray_widget = tray_widget self.main_window = main_window self.pype_info_widget = None self.log = Logger.get_logger(self.__class__.__name__) self.module_settings = get_system_settings()["modules"] self.modules_manager = TrayModulesManager() self.errors = []
def publish(paths): """Start headless publishing. Publish use json from passed paths argument. Args: paths (list): Paths to jsons. Raises: RuntimeError: When there is no pathto process. """ if not any(paths): raise RuntimeError("No publish paths specified") from openpype import install, uninstall from openpype.api import Logger # Register target and host import pyblish.api import pyblish.util env = get_app_environments_for_context(os.environ["AVALON_PROJECT"], os.environ["AVALON_ASSET"], os.environ["AVALON_TASK"], os.environ["AVALON_APP_NAME"]) os.environ.update(env) log = Logger.get_logger() install() pyblish.api.register_target("filesequence") pyblish.api.register_host("shell") os.environ["OPENPYPE_PUBLISH_DATA"] = os.pathsep.join(paths) log.info("Running publish ...") # Error exit as soon as any error occurs. error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}" for result in pyblish.util.publish_iter(): if result["error"]: log.error(error_format.format(**result)) uninstall() sys.exit(1) log.info("Publish finished.") uninstall()
import os import json import tempfile import random import string from Qt import QtWidgets, QtCore from . import DropDataFrame from .constants import HOST_NAME from avalon import io from openpype.api import execute, Logger from openpype.lib import (get_pype_execute_args, apply_project_environments_value) log = Logger().get_logger("standalonepublisher") class ComponentsWidget(QtWidgets.QWidget): def __init__(self, parent): super().__init__() self.initialized = False self.valid_components = False self.valid_family = False self.valid_repre_names = False body = QtWidgets.QWidget() self.parent_widget = parent self.drop_frame = DropDataFrame(self) buttons = QtWidgets.QWidget()
import time from openpype.api import Logger log = Logger().get_logger("SyncServer") def time_function(method): """ Decorator to print how much time function took. For debugging. Depends on presence of 'log' object """ def timed(*args, **kw): ts = time.time() result = method(*args, **kw) te = time.time() if 'log_time' in kw: name = kw.get('log_name', method.__name__.upper()) kw['log_time'][name] = int((te - ts) * 1000) else: log.debug('%r %2.2f ms' % (method.__name__, (te - ts) * 1000)) print('%r %2.2f ms' % (method.__name__, (te - ts) * 1000)) return result return timed
import sys import signal import socket from openpype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer from openpype.modules.ftrack.ftrack_server.lib import (SocketSession, SocketBaseEventHub) from openpype.modules import ModulesManager from openpype.api import Logger log = Logger().get_logger("FtrackUserServer") def main(args): port = int(args[-1]) # Create a TCP/IP socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Connect the socket to the port where the server is listening server_address = ("localhost", port) log.debug( "User Ftrack Server connected to {} port {}".format(*server_address)) sock.connect(server_address) sock.sendall(b"CreatedUser") try: session = SocketSession(auto_connect_event_hub=True, sock=sock, Eventhub=SocketBaseEventHub)
from aiohttp.web_response import Response from openpype.api import Logger log = Logger().get_logger("Event processor") class TimersManagerModuleRestApi: """ REST API endpoint used for calling from hosts when context change happens in Workfile app. """ def __init__(self, user_module, server_manager): self.module = user_module self.server_manager = server_manager self.prefix = "/timers_manager" self.register() def register(self): self.server_manager.add_route("POST", self.prefix + "/start_timer", self.start_timer) async def start_timer(self, request): data = await request.json() try: project_name = data['project_name'] asset_name = data['asset_name'] task_name = data['task_name'] hierarchy = data['hierarchy'] except KeyError:
def log(self): if self._log is None: self._log = Logger().get_logger(self.__class__.__name__) return self._log
import sys import datetime import signal import socket import pymongo import ftrack_api from openpype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer from openpype.modules.ftrack.ftrack_server.lib import ( SocketSession, StorerEventHub, TOPIC_STATUS_SERVER, TOPIC_STATUS_SERVER_RESULT) from openpype.modules.ftrack.lib import get_ftrack_event_mongo_info from openpype.lib import OpenPypeMongoConnection from openpype.api import Logger log = Logger.get_logger("Event storer") subprocess_started = datetime.datetime.now() class SessionFactory: session = None database_name, collection_name = get_ftrack_event_mongo_info() dbcon = None # ignore_topics = ["ftrack.meta.connected"] ignore_topics = [] def install_db():
#!/usr/bin/env python import time from openpype.hosts.resolve.utils import get_resolve_module from openpype.api import Logger log = Logger().get_logger(__name__) wait_delay = 2.5 wait = 0.00 ready = None while True: try: # Create project and set parameters: resolve = get_resolve_module() pm = resolve.GetProjectManager() if pm: ready = None else: ready = True except AttributeError: pass if ready is None: time.sleep(wait_delay) log.info(f"Waiting {wait}s for Resolve to have opened Project Manager") wait += wait_delay else: print(f"Preloaded variables: \n\n\tResolve module: " f"`resolve` > {type(resolve)} \n\tProject manager: " f"`pm` > {type(pm)}") break
class SocketThread(threading.Thread): """Thread that checks suprocess of storer of processor of events""" MAX_TIMEOUT = int(os.environ.get("OPENPYPE_FTRACK_SOCKET_TIMEOUT", 45)) 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)) env = os.environ.copy() env["OPENPYPE_PROCESS_MONGO_ID"] = str(Logger.mongo_process_id) # OpenPype executable (with path to start script if not build) args = get_pype_execute_args( # Add `run` command "run", self.filepath, *self.additional_args, str(self.port)) self.subproc = subprocess.Popen(args, env=env, 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)
from openpype.hosts.nuke.api.lib import (on_script_load, check_inventory_versions) import nuke from openpype.api import Logger log = Logger().get_logger(__name__) nuke.addOnScriptSave(on_script_load) nuke.addOnScriptLoad(check_inventory_versions) nuke.addOnScriptSave(check_inventory_versions) log.info('Automatic syncing of write file knob to script version')
class ProcessEventHub(SocketBaseEventHub): hearbeat_msg = b"processor" is_collection_created = False pypelog = Logger().get_logger("Session Processor") def __init__(self, *args, **kwargs): self.mongo_url = None self.dbcon = None super(ProcessEventHub, self).__init__(*args, **kwargs) def prepare_dbcon(self): try: database_name, collection_name = get_ftrack_event_mongo_info() mongo_client = OpenPypeMongoConnection.get_mongo_client() self.dbcon = mongo_client[database_name][collection_name] self.mongo_client = mongo_client except pymongo.errors.AutoReconnect: self.pypelog.error(( "Mongo server \"{}\" is not responding, exiting." ).format(OpenPypeMongoConnection.get_default_mongo_url())) sys.exit(0) except pymongo.errors.OperationFailure: self.pypelog.error(( "Error with Mongo access, probably permissions." "Check if exist database with name \"{}\"" " and collection \"{}\" inside." ).format(self.database, self.collection_name)) self.sock.sendall(b"MongoError") sys.exit(0) def wait(self, duration=None): """Overriden wait Event are loaded from Mongo DB when queue is empty. Handled event is set as processed in Mongo DB. """ started = time.time() self.prepare_dbcon() while True: try: event = self._event_queue.get(timeout=0.1) except queue.Empty: if not self.load_events(): time.sleep(0.5) else: try: self._handle(event) mongo_id = event["data"].get("_event_mongo_id") if mongo_id is None: continue self.dbcon.update_one( {"_id": mongo_id}, {"$set": {"pype_data.is_processed": True}} ) except pymongo.errors.AutoReconnect: self.pypelog.error(( "Mongo server \"{}\" is not responding, exiting." ).format(os.environ["AVALON_MONGO"])) sys.exit(0) # Additional special processing of events. if event['topic'] == 'ftrack.meta.disconnected': break if duration is not None: if (time.time() - started) > duration: break def load_events(self): """Load not processed events sorted by stored date""" ago_date = datetime.datetime.now() - datetime.timedelta(days=3) self.dbcon.delete_many({ "pype_data.stored": {"$lte": ago_date}, "pype_data.is_processed": True }) not_processed_events = self.dbcon.find( {"pype_data.is_processed": False} ).sort( [("pype_data.stored", pymongo.ASCENDING)] ) found = False for event_data in not_processed_events: new_event_data = { k: v for k, v in event_data.items() if k not in ["_id", "pype_data"] } try: event = ftrack_api.event.base.Event(**new_event_data) event["data"]["_event_mongo_id"] = event_data["_id"] except Exception: self.logger.exception(L( 'Failed to convert payload into event: {0}', event_data )) continue found = True self._event_queue.put(event) return found def _handle_packet(self, code, packet_identifier, path, data): """Override `_handle_packet` which skip events and extend heartbeat""" code_name = self._code_name_mapping[code] if code_name == "event": return return super()._handle_packet(code, packet_identifier, path, data)
import os import time import datetime import threading from Qt import QtCore, QtWidgets, QtGui import ftrack_api from ..ftrack_server.lib import check_ftrack_url from ..ftrack_server import socket_thread from ..lib import credentials from ..ftrack_module import FTRACK_MODULE_DIR from . import login_dialog from openpype.api import Logger, resources log = Logger().get_logger("FtrackModule") class FtrackTrayWrapper: def __init__(self, module): self.module = module self.thread_action_server = None self.thread_socket_server = None self.thread_timer = None self.bool_logged = False self.bool_action_server_running = False self.bool_action_thread_running = False self.bool_timer_event = False
import os import sys import avalon.api as avalon import openpype from openpype.api import Logger log = Logger().get_logger(__name__) def main(env): import openpype.hosts.resolve as bmdvr # Registers openpype's Global pyblish plugins openpype.install() # activate resolve from openpype avalon.install(bmdvr) log.info(f"Avalon registred hosts: {avalon.registered_host()}") bmdvr.launch_pype_menu() if __name__ == "__main__": result = main(os.environ) sys.exit(not bool(result))
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 = [] @staticmethod def join_query_keys(keys): """Helper to join keys to query.""" return ",".join(["\"{}\"".format(key) for key in keys]) def __init__(self, session): '''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, ftrack_server.lib.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(ftrack_server.lib.SocketSession))) self._session = session # Using decorator self.register = self.register_decorator(self.register) self.launch = self.launch_log(self.launch) # Decorator def register_decorator(self, func): @functools.wraps(func) def wrapper_register(*args, **kwargs): if self.ignore_me: return label = getattr(self, "label", self.__class__.__name__) variant = getattr(self, "variant", None) if variant: label = "{} {}".format(label, 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 = getattr(self, "label", self.__class__.__name__) variant = getattr(self, "variant", None) if variant: label = "{} {}".format(label, 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): # 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 not True: 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, session=None): 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] if session is None: session = self.session return session.query("Project where id is {}".format( project_data["id"])).one() def get_project_settings_from_event(self, event, project_name): """Load or fill OpenPype's project settings from event data. Project data are stored by ftrack id because in most cases it is easier to access project id than project name. Args: event (ftrack_api.Event): Processed event by session. project_entity (ftrack_api.Entity): Project entity. """ project_settings_by_id = event["data"].get("project_settings") if not project_settings_by_id: project_settings_by_id = {} event["data"]["project_settings"] = project_settings_by_id project_settings = project_settings_by_id.get(project_name) if not project_settings: project_settings = get_project_settings(project_name) event["data"]["project_settings"][project_name] = project_settings return project_settings @staticmethod def get_entity_path(entity): """Return full hierarchical path to entity.""" return "/".join([ent["name"] for ent in entity["link"]])
import sys import time import datetime import signal import threading import ftrack_api from openpype.api import Logger from openpype.modules import ModulesManager from openpype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer log = Logger().get_logger("Event Server Legacy") class TimerChecker(threading.Thread): max_time_out = 35 def __init__(self, server, session): self.server = server self.session = session self.is_running = False self.failed = False super().__init__() def stop(self): self.is_running = False def run(self): start = datetime.datetime.now() self.is_running = True connected = False