def test_extend(): matt = User({'type': 'facebook', 'id': 'hhextendhh' }) matt.learn_node('node-one') john = User({'type': 'linkedin', 'id': 'hhextendhh' }) john.learn_node('node-two') matt.extend(john) assert matt.dict['learned_node_ids'] == ['node-one', 'node-two']
def test_learn_node(): mo = User({'type': 'google', 'id': 'xPr' }) mo.learn_node('nonemptygraph') assert 'nonemptygraph' in mo.dict['learned_node_ids'] mo_fresh = User({'type': 'google', 'id': 'xPr' }) assert 'nonemptygraph' in mo_fresh.dict['learned_node_ids'] matt_thick = User({'type': 'github', 'id': 'ggg' }) assert 'nonemptygraph' not in matt_thick.dict['learned_node_ids']
def test_nodes_to_send(): # Need to complete! Each option works independently but I did not try combining them! MG = fill_sample_custom_nodes() pre_set = {"type":"axiom","description":"__This is the node for set__","name":"set","importance":4} sset = create_appropriate_node(pre_set) pre_vertex = {"type":"axiom","description":"__This is the node for vertex__","name":"vertex","importance":4} vertex = create_appropriate_node(pre_vertex) pre_multiset = {"type":"axiom","description":"__This is the node for multiset__","name":"multiset","importance":4} multiset = create_appropriate_node(pre_multiset) MG.add_n(sset) MG.add_n(vertex) MG.add_n(multiset) MG.add_path(['a', 'b', 'c', 'e']) MG.add_path(['b', 'd', 'e']) MG.add_edges_from([ ('set', 'a'), ('vertex', 'a'), ('multiset', 'a') ]) greg = User({'type': 'local', 'id': None}) #helpers def reset_prefs(user): # sets preferences for minimal client nodes; should send learned nodes and starting nodes only user.set_prefs({ 'subject': 'graph theory', 'goal_id': None, 'always_send_learnable_successors': False, 'always_send_learnable_pregoals': False, 'send_learnable_pregoal_number': 1, 'always_send_goal': False, 'always_send_unlearned_dependency_tree_of_goal': False, }) def reset_learned(user): for node_id in user.dict['learned_node_ids']: user.unlearn_node(node_id) # check starting nodes reset_learned(greg) reset_prefs(greg) nodes = MG.nodes_to_send(greg).difference({'set', 'multiset', 'vertex'}) assert nodes == set() greg.set_pref({'subject': None}) with pytest.raises(ValueError): MG.nodes_to_send(greg) greg.set_pref({'subject': 'not a real subject'}) with pytest.raises(ValueError): MG.nodes_to_send(greg) # check learned nodes reset_learned(greg) reset_prefs(greg) greg.learn_node('a') greg.learn_node('b') nodes = MG.nodes_to_send(greg).difference({'set', 'multiset', 'vertex'}) assert nodes == {'a', 'b'} # check absolute dominion reset_learned(greg) reset_prefs(greg) greg.learn_node('a') greg.set_pref({'always_send_learnable_successors': True}) # should now send learnable successors of learned nodes nodes = MG.nodes_to_send(greg).difference({'set', 'multiset', 'vertex'}) assert nodes == {'a', 'b'} # check learnable pregoals reset_learned(greg) reset_prefs(greg) greg.learn_node('a') greg.learn_node('b') greg.set_pref({'always_send_learnable_pregoals': True}) greg.set_pref({'goal_id': 'e'}) # using a manually set goal nodes = MG.nodes_to_send(greg).difference({'set', 'multiset', 'vertex'}) assert nodes == {'a', 'b', 'd'} # d is chosen over c because it is more important greg.set_pref({'send_learnable_pregoal_number': 2}) # ask for more than one pregoal nodes = MG.nodes_to_send(greg).difference({'set', 'multiset', 'vertex'}) assert nodes == {'a', 'b', 'c', 'd'} # c and d both chosen as pregoals greg.set_pref({'goal_id': None}) # choses a goal without setting it # in this case d should be chosen as the goal (because d and c are the only successors of learned_nodes and d is more important) greg.set_pref({'send_learnable_pregoal_number': 1}) nodes = MG.nodes_to_send(greg).difference({'set', 'multiset', 'vertex'}) assert nodes == {'a', 'b', 'd'} # d is chosen as the goal, then also set as the pregoal # check always send goal reset_learned(greg) reset_prefs(greg) greg.learn_node('a') greg.learn_node('b') greg.set_pref({'always_send_goal': True}) greg.set_pref({'goal_id': 'e'}) nodes = MG.nodes_to_send(greg).difference({'set', 'multiset', 'vertex'}) assert nodes == {'a', 'b', 'e'} # check always send dependency tree of goal reset_learned(greg) reset_prefs(greg) greg.learn_node('a') greg.learn_node('b') greg.set_pref({'always_send_unlearned_dependency_tree_of_goal': True}) greg.set_pref({'goal_id': 'e'}) nodes = MG.nodes_to_send(greg).difference({'set', 'multiset', 'vertex'}) assert nodes == {'a', 'b', 'c', 'd', 'e'}
def test_unlearn_node(): mo = User({'type': 'google', 'id': 'xPr' }) mo.learn_node('vertex') assert 'vertex' in mo.dict['learned_node_ids'] mo.unlearn_node('vertex') assert 'vertex' not in mo.dict['learned_node_ids']
class SocketHandler (WebSocketHandler): def __init__(self, application, request, **kwargs): WebSocketHandler.__init__(self, application, request, **kwargs) self.user = None def jsend(self, dic): if self.user: user_identifier = self.user.dict['account'] dic['identifier'] = user_identifier else: dic['identifier'] = None self.write_message(dic) def open(self): # this happens BEFORE first-steps self.jsend({ 'command': 'populate-oauth-urls', 'url_dict': auth.auth_url_dict(host=self.request.host), }) def on_message(self, message): log.debug('got message: ' + message+"\n") ball = json.loads(message) if ball['command'] == 'print': print(ball['message']) elif ball['command'] == 'first-steps': self.user = User(ball['identifier']) self.jsend({'command': 'update-user'}) if self.ids(ball): self.send_graph(ball) else: # they've learned nothing yet self.jsend({ 'command': 'prompt-starting-nodes', }) elif ball['command'] == 'get-starting-nodes': subject = ball['subject'] self.user.set_pref({'subject': subject}) self.send_graph(ball) elif ball['command'] == 'get-curriculum': goal = ball['goal'] elif ball['command'] == 'learn-node': command = self.user.learn_node(ball['node_id']) if command is not None: self.jsend(command) if ball['mode'] == 'learn': self.send_graph(ball) else: raise Exception('mode is not learn') elif ball['command'] == 'unlearn-node': self.user.unlearn_node(ball['node_id']) elif ball['command'] == 'set-pref': self.user.set_pref(ball['pref_dict']) elif ball['command'] == 'save-node': # hopefully this can handle both new nodes and changes to nodes node_dict = ball['node_dict'] if 'importance' in node_dict.keys(): node_dict['importance'] = int(node_dict['importance']) try: node_obj = node.create_appropriate_node(node_dict) log.debug('\nnode made. looks like: '+str(node_obj)+'. Now time to put it into the DB...\n') # take a look at the dependencies now # TODO if the node is brand new (mongo can't find it), then let previous_dep_ids = [] previous_dependency_ids = [node.reduce_string(dependency) for dependency in list(our_mongo.find({"_id": node_obj.id}))[0]["_dependencies"]] # if this works, try using set() instead of list and elimination the set()s below log.debug('prev deps are: '+str(previous_dependency_ids)) current_dependency_ids = node_obj.dependency_ids log.debug('curr deps are: '+str(current_dependency_ids)) new_dependency_ids = set(current_dependency_ids) - set(previous_dependency_ids) removed_dependency_ids = set(previous_dependency_ids) - set(current_dependency_ids) # VERIFY THAT THE GRAPH WITH THESE NEW ARCS IS STILL ACYCLIC: H = our_MG.copy() for new_dependency_id in new_dependency_ids: print('from '+str(our_MG.n(new_dependency_id))+' to '+str(node_obj)) H.add_edge(new_dependency_id, node_obj.id) H.validate(node_obj.name + ' cannot depend on ' + our_MG.n(new_dependency_id).name + ' because ' + our_MG.n(new_dependency_id).name + ' already depends on ' + node_obj.name + '!') our_mongo.upsert({ "_id": node_obj.id }, node_obj.__dict__) update_our_MG() # send an update of the graph to the user if there are new dependencies: self.request_nodes(new_dependency_ids, ball) self.remove_client_edges(node_obj.id, removed_dependency_ids) except Exception as error: # stuff didn't work, send error back to user log.warning('ERROR: '+str(error)) self.jsend({ 'command': 'display-error', 'message': str(error), }) elif ball['command'] == 're-center-graph': # We get the 5th nearest neighbors neighbors = our_MG.single_source_shortest_anydirectional_path_length(ball['central_node_id'], 1) # can just use digraph.anydirectional_neighbors H = our_MG.subgraph(list(neighbors.keys())) dict_graph = H.as_js_ready_dict() self.jsend({ 'command': 'load-graph', 'new_graph': dict_graph, }) elif ball['command'] == 'request-node': self.request_nodes([ball['node_id']], ball) elif ball['command'] == 'search': search_results = our_mongo.find({'$text':{'$search':ball['search_term']}},{'score':{'$meta':"textScore"}}) self.jsend({ 'command': 'search-results', 'results': list(search_results.sort([('score', {'$meta': 'textScore'})]).limit(10)), }) elif ball['command'] == 'get-goal-suggestion': goal_id = our_MG.choose_goal(self.user) goal_node = our_MG.n(goal_id) self.jsend({ 'command': 'suggest-goal', 'goal': goal_node.__dict__, }) elif ball['command'] == 'set-goal': goal_id = ball['goal_id'] goal_node = our_MG.n(goal_id) self.user.set_pref({'goal_id': goal_id}) self.send_graph(ball) self.jsend({ 'command': 'highlight-goal', 'goal': goal_node.__dict__, }) elif ball['command'] == 'get-pregoal-suggestion': pregoal_id = our_MG.choose_learnable_pregoals(self.user, number=1)[0] pregoal_node = our_MG.n(pregoal_id) self.jsend({ 'command': 'suggest-pregoal', 'pregoal': pregoal_node.__dict__, }) elif ball['command'] == 'set-pregoal': pregoal_id = ball['pregoal_id'] pregoal_node = our_MG.n(pregoal_id) self.user.set_pref({'requested_pregoal_id': pregoal_id}) self.send_graph(ball) self.jsend({ 'command': 'highlight-pregoal', 'pregoal': pregoal_node.__dict__, }) def request_nodes(self, node_ids, ball): # user manually requests a node. So we want to preserve client_node_ids as not to change anything for them. We only want to ADD the requested nodes additionally. for node_id in node_ids: if node_id not in our_MG.nodes(): raise ValueError('The node_id "'+node_id+'" does not exist.') ids = set(self.ids(ball)).union(set(node_ids)) H = our_MG.subgraph(ids) dict_graph = H.as_js_ready_dict() self.jsend({ 'command': 'load-graph', 'new_graph': dict_graph, }) def ids(self, ball): learned_ids = self.user.dict['learned_node_ids'] return list(set(learned_ids).union(set(ball['client_node_ids']))) def remove_client_edges(self, node_id, dependency_ids): self.jsend({ 'command': 'remove-edges', 'node_id': node_id, 'dependency_ids': list(dependency_ids), }) def send_graph(self, ball): subject = self.user.dict['prefs']['subject'] log.debug('SUBJECT IS: ' + str(subject)) log.debug('LOGGED IN AS: ' + str(self.user.identifier)) nodes_to_send = our_MG.nodes_to_send(self.user, client_node_ids=ball['client_node_ids']) subgraph_to_send = our_MG.subgraph(nodes_to_send) dict_graph = subgraph_to_send.as_js_ready_dict() self.jsend({ 'command': 'load-graph', 'new_graph': dict_graph, }) def starting_nodes(self, subject): return config.starting_nodes[subject] def on_close(self): print('A websocket has been closed.')