def _patch_objects(): """Things like Layout, Figure, and Data need to be included.""" layout_attribute_paths = [] for node, path in utils.node_generator(GRAPH_REFERENCE): if any([key in path for key in GRAPH_REFERENCE["defs"]["metaKeys"]]): continue # objects don't exist under nested meta keys if path and path[-1] == "layoutAttributes": layout_attribute_paths.append(path) for trace_name in TRACE_NAMES: OBJECTS[trace_name] = { "meta_paths": [("traces", trace_name)], "attribute_paths": [("traces", trace_name, "attributes")], "additional_attributes": {}, } OBJECTS["layout"] = { "meta_paths": [("layout",)], "attribute_paths": layout_attribute_paths, "additional_attributes": {}, } figure_attributes = { "layout": {"role": "object"}, "data": {"role": "object", "_isLinkedToArray": True}, "frames": {"role": "object", "_isLinkedToArray": True}, } OBJECTS["figure"] = {"meta_paths": [], "attribute_paths": [], "additional_attributes": figure_attributes}
def _patch_objects(): """Things like Layout, Figure, and Data need to be included.""" layout_attribute_paths = [] for node, path in utils.node_generator(GRAPH_REFERENCE): if any([key in path for key in GRAPH_REFERENCE['defs']['metaKeys']]): continue # objects don't exist under nested meta keys if path and path[-1] == 'layoutAttributes': layout_attribute_paths.append(path) for trace_name in TRACE_NAMES: OBJECTS[trace_name] = { 'meta_paths': [('traces', trace_name)], 'attribute_paths': [('traces', trace_name, 'attributes')], 'additional_attributes': {} } OBJECTS['layout'] = {'meta_paths': [('layout', )], 'attribute_paths': layout_attribute_paths, 'additional_attributes': {}} figure_attributes = {'layout': {'role': 'object'}, 'data': {'role': 'object', '_isLinkedToArray': True}} OBJECTS['figure'] = {'meta_paths': [], 'attribute_paths': [], 'additional_attributes': figure_attributes}
def assert_dict_equal(self, d1, d2, msg=None): """ Uses `np.allclose()` on number arrays. :raises: (AssertionError) Using TestCase's self.failureException """ self.assertIsInstance(d1, dict, "First argument is not a dictionary") self.assertIsInstance(d2, dict, "Second argument is not a dictionary") for node, path in node_generator(d1): # first check that this sub-dict is contained in both dicts try: comp_node = get_by_path(d2, path) except (KeyError, IndexError): standard_msg = "Path {} exists in dict 1, but not dict 2.".format( path) self.fail(self._formatMessage(msg, standard_msg)) self.assertIsInstance( comp_node, dict, "Value at path {} is not a dict.".format(path)) # check that each key in the first is contained in the second for key, val in node.items(): if isinstance(val, dict): continue # this gets tested as its own node # check that the values at this key are equal val_path = path + (key, ) try: comp_val = comp_node[key] except KeyError: standard_msg = "Path {} exists in dict 1, but not dict 2.".format( self._format_path(val_path)) self.fail(self._formatMessage(msg, standard_msg)) if isinstance(val, np.ndarray) or isinstance( comp_val, np.ndarray): if np.array_equal(val, comp_val): continue elif val == comp_val: continue if is_num_list(val) and is_num_list(comp_val): if np.allclose(val, comp_val): continue standard_msg = ("Value comparison failed at path {}.\n" "{} != {}".format(self._format_path(val_path), val, comp_val)) self.fail(self._formatMessage(msg, standard_msg)) # finally, check that keys in the second are in the first for key in comp_node: val_path = path + (key, ) if key not in node: standard_msg = "Path {} exists in dict 2, but not dict 1.".format( self._format_path(val_path)) self.fail(self._formatMessage(msg, standard_msg))
def _make_all_nodes_and_paths(self): all_nodes = list(node_generator(self['layout'])) # remove path 'second' as it's always an empty box all_paths = [] for node in all_nodes: all_paths.append(node[1]) path_second = ('second',) if path_second in all_paths: all_paths.remove(path_second) return all_nodes, all_paths
def _make_all_nodes_and_paths(self): all_nodes = list(node_generator(self['layout'])) # remove path 'second' as it's always an empty box all_paths = [] for node in all_nodes: all_paths.append(node[1]) path_second = ('second', ) if path_second in all_paths: all_paths.remove(path_second) return all_nodes, all_paths
def _make_all_nodes_and_paths(self): from plotly.utils import node_generator all_nodes = list(node_generator(self["layout"])) all_nodes.sort(key=lambda x: x[1]) # remove path 'second' as it's always an empty box all_paths = [node[1] for node in all_nodes] path_second = ("second", ) if path_second in all_paths: all_paths.remove(path_second) return all_nodes, all_paths
def _get_objects(): """ Create a reorganization of graph reference which organizes by object name. Each object can have *many* different definitions in the graph reference. These possibilities get narrowed down when we have contextual information about parent objects. For instance, Marker in Scatter has a different definition than Marker in Pie. However, we need Marker, Scatter, and Pie to exist on their own as well. Each value has the form: { 'meta_paths': [], 'attribute_paths': [], 'additional_attributes': {} } * meta_paths describes the top-most path where this object is defined * attribute_paths describes all the locations where attributes exist * additional_attributes can be used to hard-code (patch) the plot schema :return: (dict) """ objects = {} for node, path in utils.node_generator(GRAPH_REFERENCE): if any([key in path for key in GRAPH_REFERENCE['defs']['metaKeys']]): continue # objects don't exist under nested meta keys if node.get('role') != 'object': continue if 'items' in node: continue object_name = path[-1] if object_name not in objects: objects[object_name] = { 'meta_paths': [], 'attribute_paths': [], 'additional_attributes': {} } if node.get('attributes'): objects[object_name]['attribute_paths'].append(path + ('attributes', )) else: objects[object_name]['attribute_paths'].append(path) objects[object_name]['meta_paths'].append(path) return objects
def _compute_box_ids(self): box_ids_to_path = {} all_nodes = list(node_generator(self['layout'])) for node in all_nodes: if (node[1] != () and node[0]['type'] == 'box' and node[0]['boxType'] != 'empty'): try: max_id = max(box_ids_to_path.keys()) except ValueError: max_id = 0 box_ids_to_path[max_id + 1] = node[1] return box_ids_to_path
def _make_all_nodes_and_paths(self): from plotly.utils import node_generator all_nodes = list(node_generator(self['layout'])) all_nodes.sort(key=lambda x: x[1]) # remove path 'second' as it's always an empty box all_paths = [] for node in all_nodes: all_paths.append(node[1]) path_second = ('second',) if path_second in all_paths: all_paths.remove(path_second) return all_nodes, all_paths
def _compute_box_ids(self): box_ids_to_path = {} all_nodes = list(node_generator(self['layout'])) all_nodes.sort(key=lambda x: x[1]) for node in all_nodes: if (node[1] != () and node[0]['type'] == 'box' and node[0]['boxType'] != 'empty'): try: max_id = max(box_ids_to_path.keys()) except ValueError: max_id = 0 box_ids_to_path[max_id + 1] = node[1] return box_ids_to_path
def _get_objects(): """ Create a reorganization of graph reference which organizes by object name. Each object can have *many* different definitions in the graph reference. These possibilities get narrowed down when we have contextual information about parent objects. For instance, Marker in Scatter has a different definition than Marker in Pie. However, we need Marker, Scatter, and Pie to exist on their own as well. Each value has the form: { 'meta_paths': [], 'attribute_paths': [], 'additional_attributes': {} } * meta_paths describes the top-most path where this object is defined * attribute_paths describes all the locations where attributes exist * additional_attributes can be used to hard-code (patch) the plot schema :return: (dict) """ objects = {} for node, path in utils.node_generator(GRAPH_REFERENCE): if any([key in path for key in GRAPH_REFERENCE['defs']['metaKeys']]): continue # objects don't exist under nested meta keys if node.get('role') != 'object': continue if 'items' in node: continue object_name = path[-1] if object_name not in objects: objects[object_name] = {'meta_paths': [], 'attribute_paths': [], 'additional_attributes': {}} if node.get('attributes'): objects[object_name]['attribute_paths'].append( path + ('attributes', ) ) else: objects[object_name]['attribute_paths'].append(path) objects[object_name]['meta_paths'].append(path) return objects
def _compute_box_ids(self): from plotly.utils import node_generator box_ids_to_path = {} all_nodes = list(node_generator(self["layout"])) all_nodes.sort(key=lambda x: x[1]) for node in all_nodes: if (node[1] != () and node[0]["type"] == "box" and node[0]["boxType"] != "empty"): try: max_id = max(box_ids_to_path.keys()) except ValueError: max_id = 0 box_ids_to_path[max_id + 1] = node[1] return box_ids_to_path
def test_node_generator(self): # should generate a (node, path) pair for each dict in a dict node4 = {'h': 5} node3 = {'g': 7} node2 = {'e': node3} node1 = {'c': node2, 'd': ['blah']} node0 = {'a': node1, 'b': 8} expected_node_path_tuples = [(node0, ()), (node1, ('a', )), (node2, ('a', 'c')), (node3, ('a', 'c', 'e')), (node4, ('a', 'c', 'f'))] for i, item in enumerate(node_generator(node0)): self.assertEqual(item, expected_node_path_tuples[i])
def test_node_generator(self): # should generate a (node, path) pair for each dict in a dict node4 = {'h': 5} node3 = {'g': 7} node2 = {'e': node3} node1 = {'c': node2, 'd': ['blah']} node0 = {'a': node1, 'b': 8} expected_node_path_tuples = [ (node0, ()), (node1, ('a',)), (node2, ('a', 'c')), (node3, ('a', 'c', 'e')), (node4, ('a', 'c', 'f')) ] for i, item in enumerate(node_generator(node0)): self.assertEqual(item, expected_node_path_tuples[i])
def test_node_generator(self): # should generate a (node, path) pair for each dict in a dict node4 = {"h": 5} node3 = {"g": 7} node2 = {"e": node3} node1 = {"c": node2, "d": ["blah"]} node0 = {"a": node1, "b": 8} expected_node_path_tuples = [ (node0, ()), (node1, ("a",)), (node2, ("a", "c")), (node3, ("a", "c", "e")), (node4, ("a", "c", "f")), ] for i, item in enumerate(node_generator(node0)): self.assertEqual(item, expected_node_path_tuples[i])
def _get_arrays(): """Very few arrays, but this dict is the complement of OBJECTS.""" arrays = {} for node, path in utils.node_generator(GRAPH_REFERENCE): if any([key in path for key in GRAPH_REFERENCE['defs']['metaKeys']]): continue # objects don't exist under nested meta keys if node.get('role') != 'object': continue if 'items' not in node: continue object_name = path[-1] if object_name not in arrays: items = node['items'] # If items is a dict, it's anyOf them. if isinstance(items, dict): item_names = list(items.keys()) else: item_names = [object_name[:-1]] arrays[object_name] = {'meta_paths': [path], 'items': item_names} return arrays
def assert_dict_equal(self, d1, d2, msg=None): """ Uses `np.allclose()` on number arrays. :raises: (AssertionError) Using TestCase's self.failureException """ self.assertIsInstance(d1, dict, 'First argument is not a dictionary') self.assertIsInstance(d2, dict, 'Second argument is not a dictionary') for node, path in node_generator(d1): # first check that this sub-dict is contained in both dicts try: comp_node = get_by_path(d2, path) except (KeyError, IndexError): standard_msg = ( 'Path {} exists in dict 1, but not dict 2.' .format(path) ) self.fail(self._formatMessage(msg, standard_msg)) self.assertIsInstance( comp_node, dict, 'Value at path {} is not a dict.'.format(path) ) # check that each key in the first is contained in the second for key, val in node.items(): if isinstance(val, dict): continue # this gets tested as its own node # check that the values at this key are equal val_path = path + (key, ) try: comp_val = comp_node[key] except KeyError: standard_msg = ( 'Path {} exists in dict 1, but not dict 2.' .format(self._format_path(val_path)) ) self.fail(self._formatMessage(msg, standard_msg)) if (isinstance(val, np.ndarray) or isinstance(comp_val, np.ndarray)): if np.array_equal(val, comp_val): continue elif val == comp_val: continue if is_num_list(val) and is_num_list(comp_val): if np.allclose(val, comp_val): continue standard_msg = ( 'Value comparison failed at path {}.\n' '{} != {}' .format(self._format_path(val_path), val, comp_val) ) self.fail(self._formatMessage(msg, standard_msg)) # finally, check that keys in the second are in the first for key in comp_node: val_path = path + (key, ) if key not in node: standard_msg = ( 'Path {} exists in dict 2, but not dict 1.' .format(self._format_path(val_path)) ) self.fail(self._formatMessage(msg, standard_msg))