"""
Define a base class for creating a client script
"""
from twisted.internet import reactor as default_reactor
from twisted.python.failure import Failure
from twisted.internet.protocol import Factory
import sys, os
import traceback
from parlay.items.threaded_item import ThreadedItem, ITEM_PROXIES, ListenerStatus
from autobahn.twisted.websocket import WebSocketClientFactory
from parlay.protocols.websocket import WebsocketClientAdapter, WebsocketClientAdapterFactory
DEFAULT_ENGINE_WEBSOCKET_PORT = 8085
[docs]class ParlayScript(ThreadedItem):
def __init__(self, item_id=None, name=None, _reactor=None, adapter=None):
if item_id is None:
# use the full file path as the ID, default to class name if unknown
try:
item_id = "script." + os.path.abspath(sys.modules['__main__'].__file__)
except:
item_id = "script." + self.__class__.__name__ + " (Unknown File)"
if name is None:
name = self.__class__.__name__ + ".py"
# default script name and id to the name of this class
ThreadedItem.__init__(self, item_id, name, _reactor, adapter=adapter)
if adapter._connected.called:
self._reactor.callLater(0, self._start_script)
else:
adapter._connected.addCallback(lambda _: self._start_script())
[docs] def on_message(self, msg):
self._runListeners(msg)
[docs] def kill(self):
""" Kill the current script """
self.cleanup()
[docs] def cleanup(self, *args):
"""
Cleanup after running the script
:param args:
:return:
"""
def internal_cleanup():
self._adapter.transport.loseConnection()
# should we stop the reactor on close?
if self.__class__.stop_reactor_on_close:
self._reactor.stop()
self._adapter.sendClose()
self._reactor.callLater(1, internal_cleanup)
def _start_script(self):
""" Init and run the script """
# run the script and run cleanup after.
defer = self.reactor.maybeDeferToThread(self._in_thread_run_script)
defer.addBoth(self.cleanup)
def _in_thread_run_script(self):
""" Run the script. """
try:
self.run_script()
except Exception as e:
# handle any exception thrown
exc_type,exc_value,exc_traceback = sys.exc_info()
print "Exception Error: ", exc_value
print e
# print traceback, excluding this file
traceback.print_tb(exc_traceback)
# exc_strings = traceback.format_list(traceback.extract_tb(exc_traceback))
# exc_strings = [s for s in exc_strings if s.find("parlay_script.py")< 0 ]
# for s in exc_strings:
# print s
[docs] def shutdown_broker(self):
self.send_parlay_message({"TOPICS": {"type": "broker", 'request': 'shutdown'}, "CONTENTS": {}})
[docs] def run_script(self):
"""
This should be overridden by the script class
"""
raise NotImplementedError()
[docs]def start_script(script_class, engine_ip='localhost', engine_port=DEFAULT_ENGINE_WEBSOCKET_PORT,
stop_reactor_on_close=None, skip_checks=False, reactor=None):
"""
Construct a new script from the script class and start it
:param script_class : The ParlayScript class to run (Must be subclass of ParlayScript)
:param engine_ip : The ip of the broker that the script will be running on
:param engine_port : the port of the broker that the script will be running on
:param stop_reactor_on_close: Boolean regarding whether ot not to stop the reactor when the script closes
(Defaults to False if the reactor is running, True if the reactor is not currently running)
:param skip_checks : if True will not do sanity checks on script (CAREFUL: BETTER KNOW WHAT YOU ARE DOING!)
"""
if not skip_checks:
if not issubclass(script_class, ParlayScript):
raise TypeError("start_script called with: "+str(script_class)+" \n" +
"Can only call start_script on an instance of a subclass of ParlayScript")
if reactor is None:
reactor = default_reactor
# get whether to stop the reactor or not (default to the opposite of reactor running)
script_class.stop_reactor_on_close = stop_reactor_on_close if stop_reactor_on_close is not None else not reactor.running
# connect it up
factory = WebsocketClientAdapterFactory("ws://" + engine_ip + ":" + str(engine_port), reactor=reactor)
adapter = factory.adapter
script_item = script_class(_reactor=reactor, adapter=adapter)
reactor.connectTCP(engine_ip, engine_port, factory)
if not reactor.running:
reactor.run()