-
Notifications
You must be signed in to change notification settings - Fork 0
Runtime redesign proposal
This document has some ideas about how it would be cool for Runtime to work.
Algorithms are run from the runtime to implement things like header detection, formatting autofill, cell validaton, etc. These algorithms get user's input as an input, and sometimes produce actions. Sometimes they also use RangeRequest to actively request information on a spreadsheet.
As algorithms are acting on a sequence of actions, not one action is particular, they need to have a local memory. This local memory can be then stored on a server for later use and analysis. We can call algorithms together with a state they store EventHanders as they handle events that are routed from Excel add-in to the Agent.
EventHandlers should be implemented so that:
- They are easy to understand. From looking at the handler code one should easily understand what it does and what kind of information it stores.
- They are isoalted so it's easy to test them separately on simple understandible examples. Tests should consist of mock spreadsheets with a sequence of user actions.
- They should be able to access each other. For example, if some event handler needs a position of a header to operate, it needs to be able to get it.
- States of the
EventHandlercontain knowledge comnsense has about the user's spreadsheets. When we actually implement the server component to store them, it should be easy to group states by a type of handler and analyze them in bulk.
To attain these goals I propose to restrict a scope of EventHandlers to only one worksheet and let them handle all events from this sheet.
class EventHandler(object):
def handle(event, context):
""" This takes receives an Event and *immutable* context (info about workbooks, etc.)
Returns a list of Actions or None
"""
raise NotImplementedError("Abstract class method called") # PEP-3119
# All code that has anything to do with table headers should live exactly here
class HeaderDetector(EventHandler):
def __init__(self):
self.header_start_key = None
self.header_end_key = None
self.request_sent = False
# Handle an Event and return an Action or None
# /!\ The only method that can change the state of EventHandler
def handle(event, context):
if self.header is not None:
return
if not self.request_sent:
self.request_sent = True
return [ Action(type=RangeRequest,...))) ] # request values from A1 to AZ1
else:
if event.type == Event.Type.RangeResponse:
if event.rangeName == "$A$1:$E$1"
# TODO would be great if RangeResponse also had rangeName in it
# .. detect header, for example see `header_request.py:20`
self.header_start_key, self.header_end_key = "$A$1", "$E$1"
return
# Other EventHandlers can use this
# /!\ Should not change the state of EventHandler
def get_header_range(self):
return "%s:%s" % (self.header_start_key, self.header_end_key)"State" information that changes with changes in spreadsheets should be only stored inside EventHandler objects. Context as a global storage of state shared among all algorithms should only contain information about what worksheets exist and lists of event handlers hooked up to them.
class Context(object):
def __init__(self):
self.workbook = None
self.sheet_event_handers = {}
# sheet1 -> [ event_hander1 instance, event_handler2 instance, ... ]
# self.sheets
# before, Sheet objects lived here, but not sure if they are useful
# if Table-related info should now live in some EventHandlerAs long as Context only has information that doesn't change for the Worker lifetime, it can just be a global variable in Worker.
Runtime should contain instances of Handlers initialized for every workbook separately. Handers should be handling Events when the automaton is in a Ready state.
# agent\comnsense_agent\automaton\ready.py
from comnsense_agent.event_handlers import HeaderDetector, ErrorHighlighter
handler_classes = [HeaderDetector, ErrorHighlighter]
# event queue
event_queue = []
class Ready:
def next(self, msg):
if msg.is_event():
event = Event.deserialize(msg.payload)
if event.sheet not in context.sheet_event_handers:
context.sheet_event_handers[event.sheet] = [c() for c in handler_classes]
# ...
# Route the event to all the event handlers of the respecitve sheet
for handler in context.sheet_event_handers[event.sheet]:
action = handler.handle(event, context)
if action is not None:
return Message.action(action), self
# Only send Action produced by the highest-priority EventHandler
# Lower-priority handlers have to wait for higher-priority
# handlers to stop sending Actions
Sharing information between several EventHanders would look like this:
class CrossSheetAutofiller(EventHandler):
"""
Check if a user is copying data from other worksheets
If yes, copy it over automatically
Need to know where all sheet headers are
DEPENDENCY HeaderDetector
"""
def __init__(self):
self.sheet_headers = {}
def handle(event, context):
# ...
for sheet, handler_list in context.sheet_event_handers.iteritems():
# Get the sheet header
# Locate the header detector.
# `next` gets the first element from the generator expression
header_detector = next(h for h in hander_list \
if h.__class__.__name__ == 'HeaderDetector')
# LOL, or we could use OrderedDict in Context instead of lists
self.sheet_headers[sheet] = header_detector.get_header_range()