|  | #!/usr/bin/python | 
|  | # | 
|  | # Copyright (c) 2016, Alliance for Open Media. All rights reserved. | 
|  | # | 
|  | # This source code is subject to the terms of the BSD 2 Clause License and | 
|  | # the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License | 
|  | # was not distributed with this source code in the LICENSE file, you can | 
|  | # obtain it at www.aomedia.org/license/software. If the Alliance for Open | 
|  | # Media Patent License 1.0 was not distributed with this source code in the | 
|  | # PATENTS file, you can obtain it at www.aomedia.org/license/patent. | 
|  | # | 
|  |  | 
|  | """Converts Python data into data for Google Visualization API clients. | 
|  |  | 
|  | This library can be used to create a google.visualization.DataTable usable by | 
|  | visualizations built on the Google Visualization API. Output formats are raw | 
|  | JSON, JSON response, JavaScript, CSV, and HTML table. | 
|  |  | 
|  | See http://code.google.com/apis/visualization/ for documentation on the | 
|  | Google Visualization API. | 
|  | """ | 
|  |  | 
|  | __author__ = "Amit Weinstein, Misha Seltzer, Jacob Baskin" | 
|  |  | 
|  | import cgi | 
|  | import cStringIO | 
|  | import csv | 
|  | import datetime | 
|  | try: | 
|  | import json | 
|  | except ImportError: | 
|  | import simplejson as json | 
|  | import types | 
|  |  | 
|  |  | 
|  | class DataTableException(Exception): | 
|  | """The general exception object thrown by DataTable.""" | 
|  | pass | 
|  |  | 
|  |  | 
|  | class DataTableJSONEncoder(json.JSONEncoder): | 
|  | """JSON encoder that handles date/time/datetime objects correctly.""" | 
|  |  | 
|  | def __init__(self): | 
|  | json.JSONEncoder.__init__(self, | 
|  | separators=(",", ":"), | 
|  | ensure_ascii=False) | 
|  |  | 
|  | def default(self, o): | 
|  | if isinstance(o, datetime.datetime): | 
|  | if o.microsecond == 0: | 
|  | # If the time doesn't have ms-resolution, leave it out to keep | 
|  | # things smaller. | 
|  | return "Date(%d,%d,%d,%d,%d,%d)" % ( | 
|  | o.year, o.month - 1, o.day, o.hour, o.minute, o.second) | 
|  | else: | 
|  | return "Date(%d,%d,%d,%d,%d,%d,%d)" % ( | 
|  | o.year, o.month - 1, o.day, o.hour, o.minute, o.second, | 
|  | o.microsecond / 1000) | 
|  | elif isinstance(o, datetime.date): | 
|  | return "Date(%d,%d,%d)" % (o.year, o.month - 1, o.day) | 
|  | elif isinstance(o, datetime.time): | 
|  | return [o.hour, o.minute, o.second] | 
|  | else: | 
|  | return super(DataTableJSONEncoder, self).default(o) | 
|  |  | 
|  |  | 
|  | class DataTable(object): | 
|  | """Wraps the data to convert to a Google Visualization API DataTable. | 
|  |  | 
|  | Create this object, populate it with data, then call one of the ToJS... | 
|  | methods to return a string representation of the data in the format described. | 
|  |  | 
|  | You can clear all data from the object to reuse it, but you cannot clear | 
|  | individual cells, rows, or columns. You also cannot modify the table schema | 
|  | specified in the class constructor. | 
|  |  | 
|  | You can add new data one or more rows at a time. All data added to an | 
|  | instantiated DataTable must conform to the schema passed in to __init__(). | 
|  |  | 
|  | You can reorder the columns in the output table, and also specify row sorting | 
|  | order by column. The default column order is according to the original | 
|  | table_description parameter. Default row sort order is ascending, by column | 
|  | 1 values. For a dictionary, we sort the keys for order. | 
|  |  | 
|  | The data and the table_description are closely tied, as described here: | 
|  |  | 
|  | The table schema is defined in the class constructor's table_description | 
|  | parameter. The user defines each column using a tuple of | 
|  | (id[, type[, label[, custom_properties]]]). The default value for type is | 
|  | string, label is the same as ID if not specified, and custom properties is | 
|  | an empty dictionary if not specified. | 
|  |  | 
|  | table_description is a dictionary or list, containing one or more column | 
|  | descriptor tuples, nested dictionaries, and lists. Each dictionary key, list | 
|  | element, or dictionary element must eventually be defined as | 
|  | a column description tuple. Here's an example of a dictionary where the key | 
|  | is a tuple, and the value is a list of two tuples: | 
|  | {('a', 'number'): [('b', 'number'), ('c', 'string')]} | 
|  |  | 
|  | This flexibility in data entry enables you to build and manipulate your data | 
|  | in a Python structure that makes sense for your program. | 
|  |  | 
|  | Add data to the table using the same nested design as the table's | 
|  | table_description, replacing column descriptor tuples with cell data, and | 
|  | each row is an element in the top level collection. This will be a bit | 
|  | clearer after you look at the following examples showing the | 
|  | table_description, matching data, and the resulting table: | 
|  |  | 
|  | Columns as list of tuples [col1, col2, col3] | 
|  | table_description: [('a', 'number'), ('b', 'string')] | 
|  | AppendData( [[1, 'z'], [2, 'w'], [4, 'o'], [5, 'k']] ) | 
|  | Table: | 
|  | a  b   <--- these are column ids/labels | 
|  | 1  z | 
|  | 2  w | 
|  | 4  o | 
|  | 5  k | 
|  |  | 
|  | Dictionary of columns, where key is a column, and value is a list of | 
|  | columns  {col1: [col2, col3]} | 
|  | table_description: {('a', 'number'): [('b', 'number'), ('c', 'string')]} | 
|  | AppendData( data: {1: [2, 'z'], 3: [4, 'w']} | 
|  | Table: | 
|  | a  b  c | 
|  | 1  2  z | 
|  | 3  4  w | 
|  |  | 
|  | Dictionary where key is a column, and the value is itself a dictionary of | 
|  | columns {col1: {col2, col3}} | 
|  | table_description: {('a', 'number'): {'b': 'number', 'c': 'string'}} | 
|  | AppendData( data: {1: {'b': 2, 'c': 'z'}, 3: {'b': 4, 'c': 'w'}} | 
|  | Table: | 
|  | a  b  c | 
|  | 1  2  z | 
|  | 3  4  w | 
|  | """ | 
|  |  | 
|  | def __init__(self, table_description, data=None, custom_properties=None): | 
|  | """Initialize the data table from a table schema and (optionally) data. | 
|  |  | 
|  | See the class documentation for more information on table schema and data | 
|  | values. | 
|  |  | 
|  | Args: | 
|  | table_description: A table schema, following one of the formats described | 
|  | in TableDescriptionParser(). Schemas describe the | 
|  | column names, data types, and labels. See | 
|  | TableDescriptionParser() for acceptable formats. | 
|  | data: Optional. If given, fills the table with the given data. The data | 
|  | structure must be consistent with schema in table_description. See | 
|  | the class documentation for more information on acceptable data. You | 
|  | can add data later by calling AppendData(). | 
|  | custom_properties: Optional. A dictionary from string to string that | 
|  | goes into the table's custom properties. This can be | 
|  | later changed by changing self.custom_properties. | 
|  |  | 
|  | Raises: | 
|  | DataTableException: Raised if the data and the description did not match, | 
|  | or did not use the supported formats. | 
|  | """ | 
|  | self.__columns = self.TableDescriptionParser(table_description) | 
|  | self.__data = [] | 
|  | self.custom_properties = {} | 
|  | if custom_properties is not None: | 
|  | self.custom_properties = custom_properties | 
|  | if data: | 
|  | self.LoadData(data) | 
|  |  | 
|  | @staticmethod | 
|  | def CoerceValue(value, value_type): | 
|  | """Coerces a single value into the type expected for its column. | 
|  |  | 
|  | Internal helper method. | 
|  |  | 
|  | Args: | 
|  | value: The value which should be converted | 
|  | value_type: One of "string", "number", "boolean", "date", "datetime" or | 
|  | "timeofday". | 
|  |  | 
|  | Returns: | 
|  | An item of the Python type appropriate to the given value_type. Strings | 
|  | are also converted to Unicode using UTF-8 encoding if necessary. | 
|  | If a tuple is given, it should be in one of the following forms: | 
|  | - (value, formatted value) | 
|  | - (value, formatted value, custom properties) | 
|  | where the formatted value is a string, and custom properties is a | 
|  | dictionary of the custom properties for this cell. | 
|  | To specify custom properties without specifying formatted value, one can | 
|  | pass None as the formatted value. | 
|  | One can also have a null-valued cell with formatted value and/or custom | 
|  | properties by specifying None for the value. | 
|  | This method ignores the custom properties except for checking that it is a | 
|  | dictionary. The custom properties are handled in the ToJSon and ToJSCode | 
|  | methods. | 
|  | The real type of the given value is not strictly checked. For example, | 
|  | any type can be used for string - as we simply take its str( ) and for | 
|  | boolean value we just check "if value". | 
|  | Examples: | 
|  | CoerceValue(None, "string") returns None | 
|  | CoerceValue((5, "5$"), "number") returns (5, "5$") | 
|  | CoerceValue(100, "string") returns "100" | 
|  | CoerceValue(0, "boolean") returns False | 
|  |  | 
|  | Raises: | 
|  | DataTableException: The value and type did not match in a not-recoverable | 
|  | way, for example given value 'abc' for type 'number'. | 
|  | """ | 
|  | if isinstance(value, tuple): | 
|  | # In case of a tuple, we run the same function on the value itself and | 
|  | # add the formatted value. | 
|  | if (len(value) not in [2, 3] or | 
|  | (len(value) == 3 and not isinstance(value[2], dict))): | 
|  | raise DataTableException("Wrong format for value and formatting - %s." % | 
|  | str(value)) | 
|  | if not isinstance(value[1], types.StringTypes + (types.NoneType,)): | 
|  | raise DataTableException("Formatted value is not string, given %s." % | 
|  | type(value[1])) | 
|  | js_value = DataTable.CoerceValue(value[0], value_type) | 
|  | return (js_value,) + value[1:] | 
|  |  | 
|  | t_value = type(value) | 
|  | if value is None: | 
|  | return value | 
|  | if value_type == "boolean": | 
|  | return bool(value) | 
|  |  | 
|  | elif value_type == "number": | 
|  | if isinstance(value, (int, long, float)): | 
|  | return value | 
|  | raise DataTableException("Wrong type %s when expected number" % t_value) | 
|  |  | 
|  | elif value_type == "string": | 
|  | if isinstance(value, unicode): | 
|  | return value | 
|  | else: | 
|  | return str(value).decode("utf-8") | 
|  |  | 
|  | elif value_type == "date": | 
|  | if isinstance(value, datetime.datetime): | 
|  | return datetime.date(value.year, value.month, value.day) | 
|  | elif isinstance(value, datetime.date): | 
|  | return value | 
|  | else: | 
|  | raise DataTableException("Wrong type %s when expected date" % t_value) | 
|  |  | 
|  | elif value_type == "timeofday": | 
|  | if isinstance(value, datetime.datetime): | 
|  | return datetime.time(value.hour, value.minute, value.second) | 
|  | elif isinstance(value, datetime.time): | 
|  | return value | 
|  | else: | 
|  | raise DataTableException("Wrong type %s when expected time" % t_value) | 
|  |  | 
|  | elif value_type == "datetime": | 
|  | if isinstance(value, datetime.datetime): | 
|  | return value | 
|  | else: | 
|  | raise DataTableException("Wrong type %s when expected datetime" % | 
|  | t_value) | 
|  | # If we got here, it means the given value_type was not one of the | 
|  | # supported types. | 
|  | raise DataTableException("Unsupported type %s" % value_type) | 
|  |  | 
|  | @staticmethod | 
|  | def EscapeForJSCode(encoder, value): | 
|  | if value is None: | 
|  | return "null" | 
|  | elif isinstance(value, datetime.datetime): | 
|  | if value.microsecond == 0: | 
|  | # If it's not ms-resolution, leave that out to save space. | 
|  | return "new Date(%d,%d,%d,%d,%d,%d)" % (value.year, | 
|  | value.month - 1,  # To match JS | 
|  | value.day, | 
|  | value.hour, | 
|  | value.minute, | 
|  | value.second) | 
|  | else: | 
|  | return "new Date(%d,%d,%d,%d,%d,%d,%d)" % (value.year, | 
|  | value.month - 1,  # match JS | 
|  | value.day, | 
|  | value.hour, | 
|  | value.minute, | 
|  | value.second, | 
|  | value.microsecond / 1000) | 
|  | elif isinstance(value, datetime.date): | 
|  | return "new Date(%d,%d,%d)" % (value.year, value.month - 1, value.day) | 
|  | else: | 
|  | return encoder.encode(value) | 
|  |  | 
|  | @staticmethod | 
|  | def ToString(value): | 
|  | if value is None: | 
|  | return "(empty)" | 
|  | elif isinstance(value, (datetime.datetime, | 
|  | datetime.date, | 
|  | datetime.time)): | 
|  | return str(value) | 
|  | elif isinstance(value, unicode): | 
|  | return value | 
|  | elif isinstance(value, bool): | 
|  | return str(value).lower() | 
|  | else: | 
|  | return str(value).decode("utf-8") | 
|  |  | 
|  | @staticmethod | 
|  | def ColumnTypeParser(description): | 
|  | """Parses a single column description. Internal helper method. | 
|  |  | 
|  | Args: | 
|  | description: a column description in the possible formats: | 
|  | 'id' | 
|  | ('id',) | 
|  | ('id', 'type') | 
|  | ('id', 'type', 'label') | 
|  | ('id', 'type', 'label', {'custom_prop1': 'custom_val1'}) | 
|  | Returns: | 
|  | Dictionary with the following keys: id, label, type, and | 
|  | custom_properties where: | 
|  | - If label not given, it equals the id. | 
|  | - If type not given, string is used by default. | 
|  | - If custom properties are not given, an empty dictionary is used by | 
|  | default. | 
|  |  | 
|  | Raises: | 
|  | DataTableException: The column description did not match the RE, or | 
|  | unsupported type was passed. | 
|  | """ | 
|  | if not description: | 
|  | raise DataTableException("Description error: empty description given") | 
|  |  | 
|  | if not isinstance(description, (types.StringTypes, tuple)): | 
|  | raise DataTableException("Description error: expected either string or " | 
|  | "tuple, got %s." % type(description)) | 
|  |  | 
|  | if isinstance(description, types.StringTypes): | 
|  | description = (description,) | 
|  |  | 
|  | # According to the tuple's length, we fill the keys | 
|  | # We verify everything is of type string | 
|  | for elem in description[:3]: | 
|  | if not isinstance(elem, types.StringTypes): | 
|  | raise DataTableException("Description error: expected tuple of " | 
|  | "strings, current element of type %s." % | 
|  | type(elem)) | 
|  | desc_dict = {"id": description[0], | 
|  | "label": description[0], | 
|  | "type": "string", | 
|  | "custom_properties": {}} | 
|  | if len(description) > 1: | 
|  | desc_dict["type"] = description[1].lower() | 
|  | if len(description) > 2: | 
|  | desc_dict["label"] = description[2] | 
|  | if len(description) > 3: | 
|  | if not isinstance(description[3], dict): | 
|  | raise DataTableException("Description error: expected custom " | 
|  | "properties of type dict, current element " | 
|  | "of type %s." % type(description[3])) | 
|  | desc_dict["custom_properties"] = description[3] | 
|  | if len(description) > 4: | 
|  | raise DataTableException("Description error: tuple of length > 4") | 
|  | if desc_dict["type"] not in ["string", "number", "boolean", | 
|  | "date", "datetime", "timeofday"]: | 
|  | raise DataTableException( | 
|  | "Description error: unsupported type '%s'" % desc_dict["type"]) | 
|  | return desc_dict | 
|  |  | 
|  | @staticmethod | 
|  | def TableDescriptionParser(table_description, depth=0): | 
|  | """Parses the table_description object for internal use. | 
|  |  | 
|  | Parses the user-submitted table description into an internal format used | 
|  | by the Python DataTable class. Returns the flat list of parsed columns. | 
|  |  | 
|  | Args: | 
|  | table_description: A description of the table which should comply | 
|  | with one of the formats described below. | 
|  | depth: Optional. The depth of the first level in the current description. | 
|  | Used by recursive calls to this function. | 
|  |  | 
|  | Returns: | 
|  | List of columns, where each column represented by a dictionary with the | 
|  | keys: id, label, type, depth, container which means the following: | 
|  | - id: the id of the column | 
|  | - name: The name of the column | 
|  | - type: The datatype of the elements in this column. Allowed types are | 
|  | described in ColumnTypeParser(). | 
|  | - depth: The depth of this column in the table description | 
|  | - container: 'dict', 'iter' or 'scalar' for parsing the format easily. | 
|  | - custom_properties: The custom properties for this column. | 
|  | The returned description is flattened regardless of how it was given. | 
|  |  | 
|  | Raises: | 
|  | DataTableException: Error in a column description or in the description | 
|  | structure. | 
|  |  | 
|  | Examples: | 
|  | A column description can be of the following forms: | 
|  | 'id' | 
|  | ('id',) | 
|  | ('id', 'type') | 
|  | ('id', 'type', 'label') | 
|  | ('id', 'type', 'label', {'custom_prop1': 'custom_val1'}) | 
|  | or as a dictionary: | 
|  | 'id': 'type' | 
|  | 'id': ('type',) | 
|  | 'id': ('type', 'label') | 
|  | 'id': ('type', 'label', {'custom_prop1': 'custom_val1'}) | 
|  | If the type is not specified, we treat it as string. | 
|  | If no specific label is given, the label is simply the id. | 
|  | If no custom properties are given, we use an empty dictionary. | 
|  |  | 
|  | input: [('a', 'date'), ('b', 'timeofday', 'b', {'foo': 'bar'})] | 
|  | output: [{'id': 'a', 'label': 'a', 'type': 'date', | 
|  | 'depth': 0, 'container': 'iter', 'custom_properties': {}}, | 
|  | {'id': 'b', 'label': 'b', 'type': 'timeofday', | 
|  | 'depth': 0, 'container': 'iter', | 
|  | 'custom_properties': {'foo': 'bar'}}] | 
|  |  | 
|  | input: {'a': [('b', 'number'), ('c', 'string', 'column c')]} | 
|  | output: [{'id': 'a', 'label': 'a', 'type': 'string', | 
|  | 'depth': 0, 'container': 'dict', 'custom_properties': {}}, | 
|  | {'id': 'b', 'label': 'b', 'type': 'number', | 
|  | 'depth': 1, 'container': 'iter', 'custom_properties': {}}, | 
|  | {'id': 'c', 'label': 'column c', 'type': 'string', | 
|  | 'depth': 1, 'container': 'iter', 'custom_properties': {}}] | 
|  |  | 
|  | input:  {('a', 'number', 'column a'): { 'b': 'number', 'c': 'string'}} | 
|  | output: [{'id': 'a', 'label': 'column a', 'type': 'number', | 
|  | 'depth': 0, 'container': 'dict', 'custom_properties': {}}, | 
|  | {'id': 'b', 'label': 'b', 'type': 'number', | 
|  | 'depth': 1, 'container': 'dict', 'custom_properties': {}}, | 
|  | {'id': 'c', 'label': 'c', 'type': 'string', | 
|  | 'depth': 1, 'container': 'dict', 'custom_properties': {}}] | 
|  |  | 
|  | input: { ('w', 'string', 'word'): ('c', 'number', 'count') } | 
|  | output: [{'id': 'w', 'label': 'word', 'type': 'string', | 
|  | 'depth': 0, 'container': 'dict', 'custom_properties': {}}, | 
|  | {'id': 'c', 'label': 'count', 'type': 'number', | 
|  | 'depth': 1, 'container': 'scalar', 'custom_properties': {}}] | 
|  |  | 
|  | input: {'a': ('number', 'column a'), 'b': ('string', 'column b')} | 
|  | output: [{'id': 'a', 'label': 'column a', 'type': 'number', 'depth': 0, | 
|  | 'container': 'dict', 'custom_properties': {}}, | 
|  | {'id': 'b', 'label': 'column b', 'type': 'string', 'depth': 0, | 
|  | 'container': 'dict', 'custom_properties': {}} | 
|  |  | 
|  | NOTE: there might be ambiguity in the case of a dictionary representation | 
|  | of a single column. For example, the following description can be parsed | 
|  | in 2 different ways: {'a': ('b', 'c')} can be thought of a single column | 
|  | with the id 'a', of type 'b' and the label 'c', or as 2 columns: one named | 
|  | 'a', and the other named 'b' of type 'c'. We choose the first option by | 
|  | default, and in case the second option is the right one, it is possible to | 
|  | make the key into a tuple (i.e. {('a',): ('b', 'c')}) or add more info | 
|  | into the tuple, thus making it look like this: {'a': ('b', 'c', 'b', {})} | 
|  | -- second 'b' is the label, and {} is the custom properties field. | 
|  | """ | 
|  | # For the recursion step, we check for a scalar object (string or tuple) | 
|  | if isinstance(table_description, (types.StringTypes, tuple)): | 
|  | parsed_col = DataTable.ColumnTypeParser(table_description) | 
|  | parsed_col["depth"] = depth | 
|  | parsed_col["container"] = "scalar" | 
|  | return [parsed_col] | 
|  |  | 
|  | # Since it is not scalar, table_description must be iterable. | 
|  | if not hasattr(table_description, "__iter__"): | 
|  | raise DataTableException("Expected an iterable object, got %s" % | 
|  | type(table_description)) | 
|  | if not isinstance(table_description, dict): | 
|  | # We expects a non-dictionary iterable item. | 
|  | columns = [] | 
|  | for desc in table_description: | 
|  | parsed_col = DataTable.ColumnTypeParser(desc) | 
|  | parsed_col["depth"] = depth | 
|  | parsed_col["container"] = "iter" | 
|  | columns.append(parsed_col) | 
|  | if not columns: | 
|  | raise DataTableException("Description iterable objects should not" | 
|  | " be empty.") | 
|  | return columns | 
|  | # The other case is a dictionary | 
|  | if not table_description: | 
|  | raise DataTableException("Empty dictionaries are not allowed inside" | 
|  | " description") | 
|  |  | 
|  | # To differentiate between the two cases of more levels below or this is | 
|  | # the most inner dictionary, we consider the number of keys (more then one | 
|  | # key is indication for most inner dictionary) and the type of the key and | 
|  | # value in case of only 1 key (if the type of key is string and the type of | 
|  | # the value is a tuple of 0-3 items, we assume this is the most inner | 
|  | # dictionary). | 
|  | # NOTE: this way of differentiating might create ambiguity. See docs. | 
|  | if (len(table_description) != 1 or | 
|  | (isinstance(table_description.keys()[0], types.StringTypes) and | 
|  | isinstance(table_description.values()[0], tuple) and | 
|  | len(table_description.values()[0]) < 4)): | 
|  | # This is the most inner dictionary. Parsing types. | 
|  | columns = [] | 
|  | # We sort the items, equivalent to sort the keys since they are unique | 
|  | for key, value in sorted(table_description.items()): | 
|  | # We parse the column type as (key, type) or (key, type, label) using | 
|  | # ColumnTypeParser. | 
|  | if isinstance(value, tuple): | 
|  | parsed_col = DataTable.ColumnTypeParser((key,) + value) | 
|  | else: | 
|  | parsed_col = DataTable.ColumnTypeParser((key, value)) | 
|  | parsed_col["depth"] = depth | 
|  | parsed_col["container"] = "dict" | 
|  | columns.append(parsed_col) | 
|  | return columns | 
|  | # This is an outer dictionary, must have at most one key. | 
|  | parsed_col = DataTable.ColumnTypeParser(table_description.keys()[0]) | 
|  | parsed_col["depth"] = depth | 
|  | parsed_col["container"] = "dict" | 
|  | return ([parsed_col] + | 
|  | DataTable.TableDescriptionParser(table_description.values()[0], | 
|  | depth=depth + 1)) | 
|  |  | 
|  | @property | 
|  | def columns(self): | 
|  | """Returns the parsed table description.""" | 
|  | return self.__columns | 
|  |  | 
|  | def NumberOfRows(self): | 
|  | """Returns the number of rows in the current data stored in the table.""" | 
|  | return len(self.__data) | 
|  |  | 
|  | def SetRowsCustomProperties(self, rows, custom_properties): | 
|  | """Sets the custom properties for given row(s). | 
|  |  | 
|  | Can accept a single row or an iterable of rows. | 
|  | Sets the given custom properties for all specified rows. | 
|  |  | 
|  | Args: | 
|  | rows: The row, or rows, to set the custom properties for. | 
|  | custom_properties: A string to string dictionary of custom properties to | 
|  | set for all rows. | 
|  | """ | 
|  | if not hasattr(rows, "__iter__"): | 
|  | rows = [rows] | 
|  | for row in rows: | 
|  | self.__data[row] = (self.__data[row][0], custom_properties) | 
|  |  | 
|  | def LoadData(self, data, custom_properties=None): | 
|  | """Loads new rows to the data table, clearing existing rows. | 
|  |  | 
|  | May also set the custom_properties for the added rows. The given custom | 
|  | properties dictionary specifies the dictionary that will be used for *all* | 
|  | given rows. | 
|  |  | 
|  | Args: | 
|  | data: The rows that the table will contain. | 
|  | custom_properties: A dictionary of string to string to set as the custom | 
|  | properties for all rows. | 
|  | """ | 
|  | self.__data = [] | 
|  | self.AppendData(data, custom_properties) | 
|  |  | 
|  | def AppendData(self, data, custom_properties=None): | 
|  | """Appends new data to the table. | 
|  |  | 
|  | Data is appended in rows. Data must comply with | 
|  | the table schema passed in to __init__(). See CoerceValue() for a list | 
|  | of acceptable data types. See the class documentation for more information | 
|  | and examples of schema and data values. | 
|  |  | 
|  | Args: | 
|  | data: The row to add to the table. The data must conform to the table | 
|  | description format. | 
|  | custom_properties: A dictionary of string to string, representing the | 
|  | custom properties to add to all the rows. | 
|  |  | 
|  | Raises: | 
|  | DataTableException: The data structure does not match the description. | 
|  | """ | 
|  | # If the maximal depth is 0, we simply iterate over the data table | 
|  | # lines and insert them using _InnerAppendData. Otherwise, we simply | 
|  | # let the _InnerAppendData handle all the levels. | 
|  | if not self.__columns[-1]["depth"]: | 
|  | for row in data: | 
|  | self._InnerAppendData(({}, custom_properties), row, 0) | 
|  | else: | 
|  | self._InnerAppendData(({}, custom_properties), data, 0) | 
|  |  | 
|  | def _InnerAppendData(self, prev_col_values, data, col_index): | 
|  | """Inner function to assist LoadData.""" | 
|  | # We first check that col_index has not exceeded the columns size | 
|  | if col_index >= len(self.__columns): | 
|  | raise DataTableException("The data does not match description, too deep") | 
|  |  | 
|  | # Dealing with the scalar case, the data is the last value. | 
|  | if self.__columns[col_index]["container"] == "scalar": | 
|  | prev_col_values[0][self.__columns[col_index]["id"]] = data | 
|  | self.__data.append(prev_col_values) | 
|  | return | 
|  |  | 
|  | if self.__columns[col_index]["container"] == "iter": | 
|  | if not hasattr(data, "__iter__") or isinstance(data, dict): | 
|  | raise DataTableException("Expected iterable object, got %s" % | 
|  | type(data)) | 
|  | # We only need to insert the rest of the columns | 
|  | # If there are less items than expected, we only add what there is. | 
|  | for value in data: | 
|  | if col_index >= len(self.__columns): | 
|  | raise DataTableException("Too many elements given in data") | 
|  | prev_col_values[0][self.__columns[col_index]["id"]] = value | 
|  | col_index += 1 | 
|  | self.__data.append(prev_col_values) | 
|  | return | 
|  |  | 
|  | # We know the current level is a dictionary, we verify the type. | 
|  | if not isinstance(data, dict): | 
|  | raise DataTableException("Expected dictionary at current level, got %s" % | 
|  | type(data)) | 
|  | # We check if this is the last level | 
|  | if self.__columns[col_index]["depth"] == self.__columns[-1]["depth"]: | 
|  | # We need to add the keys in the dictionary as they are | 
|  | for col in self.__columns[col_index:]: | 
|  | if col["id"] in data: | 
|  | prev_col_values[0][col["id"]] = data[col["id"]] | 
|  | self.__data.append(prev_col_values) | 
|  | return | 
|  |  | 
|  | # We have a dictionary in an inner depth level. | 
|  | if not data.keys(): | 
|  | # In case this is an empty dictionary, we add a record with the columns | 
|  | # filled only until this point. | 
|  | self.__data.append(prev_col_values) | 
|  | else: | 
|  | for key in sorted(data): | 
|  | col_values = dict(prev_col_values[0]) | 
|  | col_values[self.__columns[col_index]["id"]] = key | 
|  | self._InnerAppendData((col_values, prev_col_values[1]), | 
|  | data[key], col_index + 1) | 
|  |  | 
|  | def _PreparedData(self, order_by=()): | 
|  | """Prepares the data for enumeration - sorting it by order_by. | 
|  |  | 
|  | Args: | 
|  | order_by: Optional. Specifies the name of the column(s) to sort by, and | 
|  | (optionally) which direction to sort in. Default sort direction | 
|  | is asc. Following formats are accepted: | 
|  | "string_col_name"  -- For a single key in default (asc) order. | 
|  | ("string_col_name", "asc|desc") -- For a single key. | 
|  | [("col_1","asc|desc"), ("col_2","asc|desc")] -- For more than | 
|  | one column, an array of tuples of (col_name, "asc|desc"). | 
|  |  | 
|  | Returns: | 
|  | The data sorted by the keys given. | 
|  |  | 
|  | Raises: | 
|  | DataTableException: Sort direction not in 'asc' or 'desc' | 
|  | """ | 
|  | if not order_by: | 
|  | return self.__data | 
|  |  | 
|  | proper_sort_keys = [] | 
|  | if isinstance(order_by, types.StringTypes) or ( | 
|  | isinstance(order_by, tuple) and len(order_by) == 2 and | 
|  | order_by[1].lower() in ["asc", "desc"]): | 
|  | order_by = (order_by,) | 
|  | for key in order_by: | 
|  | if isinstance(key, types.StringTypes): | 
|  | proper_sort_keys.append((key, 1)) | 
|  | elif (isinstance(key, (list, tuple)) and len(key) == 2 and | 
|  | key[1].lower() in ("asc", "desc")): | 
|  | proper_sort_keys.append((key[0], key[1].lower() == "asc" and 1 or -1)) | 
|  | else: | 
|  | raise DataTableException("Expected tuple with second value: " | 
|  | "'asc' or 'desc'") | 
|  |  | 
|  | def SortCmpFunc(row1, row2): | 
|  | """cmp function for sorted. Compares by keys and 'asc'/'desc' keywords.""" | 
|  | for key, asc_mult in proper_sort_keys: | 
|  | cmp_result = asc_mult * cmp(row1[0].get(key), row2[0].get(key)) | 
|  | if cmp_result: | 
|  | return cmp_result | 
|  | return 0 | 
|  |  | 
|  | return sorted(self.__data, cmp=SortCmpFunc) | 
|  |  | 
|  | def ToJSCode(self, name, columns_order=None, order_by=()): | 
|  | """Writes the data table as a JS code string. | 
|  |  | 
|  | This method writes a string of JS code that can be run to | 
|  | generate a DataTable with the specified data. Typically used for debugging | 
|  | only. | 
|  |  | 
|  | Args: | 
|  | name: The name of the table. The name would be used as the DataTable's | 
|  | variable name in the created JS code. | 
|  | columns_order: Optional. Specifies the order of columns in the | 
|  | output table. Specify a list of all column IDs in the order | 
|  | in which you want the table created. | 
|  | Note that you must list all column IDs in this parameter, | 
|  | if you use it. | 
|  | order_by: Optional. Specifies the name of the column(s) to sort by. | 
|  | Passed as is to _PreparedData. | 
|  |  | 
|  | Returns: | 
|  | A string of JS code that, when run, generates a DataTable with the given | 
|  | name and the data stored in the DataTable object. | 
|  | Example result: | 
|  | "var tab1 = new google.visualization.DataTable(); | 
|  | tab1.addColumn("string", "a", "a"); | 
|  | tab1.addColumn("number", "b", "b"); | 
|  | tab1.addColumn("boolean", "c", "c"); | 
|  | tab1.addRows(10); | 
|  | tab1.setCell(0, 0, "a"); | 
|  | tab1.setCell(0, 1, 1, null, {"foo": "bar"}); | 
|  | tab1.setCell(0, 2, true); | 
|  | ... | 
|  | tab1.setCell(9, 0, "c"); | 
|  | tab1.setCell(9, 1, 3, "3$"); | 
|  | tab1.setCell(9, 2, false);" | 
|  |  | 
|  | Raises: | 
|  | DataTableException: The data does not match the type. | 
|  | """ | 
|  |  | 
|  | encoder = DataTableJSONEncoder() | 
|  |  | 
|  | if columns_order is None: | 
|  | columns_order = [col["id"] for col in self.__columns] | 
|  | col_dict = dict([(col["id"], col) for col in self.__columns]) | 
|  |  | 
|  | # We first create the table with the given name | 
|  | jscode = "var %s = new google.visualization.DataTable();\n" % name | 
|  | if self.custom_properties: | 
|  | jscode += "%s.setTableProperties(%s);\n" % ( | 
|  | name, encoder.encode(self.custom_properties)) | 
|  |  | 
|  | # We add the columns to the table | 
|  | for i, col in enumerate(columns_order): | 
|  | jscode += "%s.addColumn(%s, %s, %s);\n" % ( | 
|  | name, | 
|  | encoder.encode(col_dict[col]["type"]), | 
|  | encoder.encode(col_dict[col]["label"]), | 
|  | encoder.encode(col_dict[col]["id"])) | 
|  | if col_dict[col]["custom_properties"]: | 
|  | jscode += "%s.setColumnProperties(%d, %s);\n" % ( | 
|  | name, i, encoder.encode(col_dict[col]["custom_properties"])) | 
|  | jscode += "%s.addRows(%d);\n" % (name, len(self.__data)) | 
|  |  | 
|  | # We now go over the data and add each row | 
|  | for (i, (row, cp)) in enumerate(self._PreparedData(order_by)): | 
|  | # We add all the elements of this row by their order | 
|  | for (j, col) in enumerate(columns_order): | 
|  | if col not in row or row[col] is None: | 
|  | continue | 
|  | value = self.CoerceValue(row[col], col_dict[col]["type"]) | 
|  | if isinstance(value, tuple): | 
|  | cell_cp = "" | 
|  | if len(value) == 3: | 
|  | cell_cp = ", %s" % encoder.encode(row[col][2]) | 
|  | # We have a formatted value or custom property as well | 
|  | jscode += ("%s.setCell(%d, %d, %s, %s%s);\n" % | 
|  | (name, i, j, | 
|  | self.EscapeForJSCode(encoder, value[0]), | 
|  | self.EscapeForJSCode(encoder, value[1]), cell_cp)) | 
|  | else: | 
|  | jscode += "%s.setCell(%d, %d, %s);\n" % ( | 
|  | name, i, j, self.EscapeForJSCode(encoder, value)) | 
|  | if cp: | 
|  | jscode += "%s.setRowProperties(%d, %s);\n" % ( | 
|  | name, i, encoder.encode(cp)) | 
|  | return jscode | 
|  |  | 
|  | def ToHtml(self, columns_order=None, order_by=()): | 
|  | """Writes the data table as an HTML table code string. | 
|  |  | 
|  | Args: | 
|  | columns_order: Optional. Specifies the order of columns in the | 
|  | output table. Specify a list of all column IDs in the order | 
|  | in which you want the table created. | 
|  | Note that you must list all column IDs in this parameter, | 
|  | if you use it. | 
|  | order_by: Optional. Specifies the name of the column(s) to sort by. | 
|  | Passed as is to _PreparedData. | 
|  |  | 
|  | Returns: | 
|  | An HTML table code string. | 
|  | Example result (the result is without the newlines): | 
|  | <html><body><table border="1"> | 
|  | <thead><tr><th>a</th><th>b</th><th>c</th></tr></thead> | 
|  | <tbody> | 
|  | <tr><td>1</td><td>"z"</td><td>2</td></tr> | 
|  | <tr><td>"3$"</td><td>"w"</td><td></td></tr> | 
|  | </tbody> | 
|  | </table></body></html> | 
|  |  | 
|  | Raises: | 
|  | DataTableException: The data does not match the type. | 
|  | """ | 
|  | table_template = "<html><body><table border=\"1\">%s</table></body></html>" | 
|  | columns_template = "<thead><tr>%s</tr></thead>" | 
|  | rows_template = "<tbody>%s</tbody>" | 
|  | row_template = "<tr>%s</tr>" | 
|  | header_cell_template = "<th>%s</th>" | 
|  | cell_template = "<td>%s</td>" | 
|  |  | 
|  | if columns_order is None: | 
|  | columns_order = [col["id"] for col in self.__columns] | 
|  | col_dict = dict([(col["id"], col) for col in self.__columns]) | 
|  |  | 
|  | columns_list = [] | 
|  | for col in columns_order: | 
|  | columns_list.append(header_cell_template % | 
|  | cgi.escape(col_dict[col]["label"])) | 
|  | columns_html = columns_template % "".join(columns_list) | 
|  |  | 
|  | rows_list = [] | 
|  | # We now go over the data and add each row | 
|  | for row, unused_cp in self._PreparedData(order_by): | 
|  | cells_list = [] | 
|  | # We add all the elements of this row by their order | 
|  | for col in columns_order: | 
|  | # For empty string we want empty quotes (""). | 
|  | value = "" | 
|  | if col in row and row[col] is not None: | 
|  | value = self.CoerceValue(row[col], col_dict[col]["type"]) | 
|  | if isinstance(value, tuple): | 
|  | # We have a formatted value and we're going to use it | 
|  | cells_list.append(cell_template % cgi.escape(self.ToString(value[1]))) | 
|  | else: | 
|  | cells_list.append(cell_template % cgi.escape(self.ToString(value))) | 
|  | rows_list.append(row_template % "".join(cells_list)) | 
|  | rows_html = rows_template % "".join(rows_list) | 
|  |  | 
|  | return table_template % (columns_html + rows_html) | 
|  |  | 
|  | def ToCsv(self, columns_order=None, order_by=(), separator=","): | 
|  | """Writes the data table as a CSV string. | 
|  |  | 
|  | Output is encoded in UTF-8 because the Python "csv" module can't handle | 
|  | Unicode properly according to its documentation. | 
|  |  | 
|  | Args: | 
|  | columns_order: Optional. Specifies the order of columns in the | 
|  | output table. Specify a list of all column IDs in the order | 
|  | in which you want the table created. | 
|  | Note that you must list all column IDs in this parameter, | 
|  | if you use it. | 
|  | order_by: Optional. Specifies the name of the column(s) to sort by. | 
|  | Passed as is to _PreparedData. | 
|  | separator: Optional. The separator to use between the values. | 
|  |  | 
|  | Returns: | 
|  | A CSV string representing the table. | 
|  | Example result: | 
|  | 'a','b','c' | 
|  | 1,'z',2 | 
|  | 3,'w','' | 
|  |  | 
|  | Raises: | 
|  | DataTableException: The data does not match the type. | 
|  | """ | 
|  |  | 
|  | csv_buffer = cStringIO.StringIO() | 
|  | writer = csv.writer(csv_buffer, delimiter=separator) | 
|  |  | 
|  | if columns_order is None: | 
|  | columns_order = [col["id"] for col in self.__columns] | 
|  | col_dict = dict([(col["id"], col) for col in self.__columns]) | 
|  |  | 
|  | writer.writerow([col_dict[col]["label"].encode("utf-8") | 
|  | for col in columns_order]) | 
|  |  | 
|  | # We now go over the data and add each row | 
|  | for row, unused_cp in self._PreparedData(order_by): | 
|  | cells_list = [] | 
|  | # We add all the elements of this row by their order | 
|  | for col in columns_order: | 
|  | value = "" | 
|  | if col in row and row[col] is not None: | 
|  | value = self.CoerceValue(row[col], col_dict[col]["type"]) | 
|  | if isinstance(value, tuple): | 
|  | # We have a formatted value. Using it only for date/time types. | 
|  | if col_dict[col]["type"] in ["date", "datetime", "timeofday"]: | 
|  | cells_list.append(self.ToString(value[1]).encode("utf-8")) | 
|  | else: | 
|  | cells_list.append(self.ToString(value[0]).encode("utf-8")) | 
|  | else: | 
|  | cells_list.append(self.ToString(value).encode("utf-8")) | 
|  | writer.writerow(cells_list) | 
|  | return csv_buffer.getvalue() | 
|  |  | 
|  | def ToTsvExcel(self, columns_order=None, order_by=()): | 
|  | """Returns a file in tab-separated-format readable by MS Excel. | 
|  |  | 
|  | Returns a file in UTF-16 little endian encoding, with tabs separating the | 
|  | values. | 
|  |  | 
|  | Args: | 
|  | columns_order: Delegated to ToCsv. | 
|  | order_by: Delegated to ToCsv. | 
|  |  | 
|  | Returns: | 
|  | A tab-separated little endian UTF16 file representing the table. | 
|  | """ | 
|  | return (self.ToCsv(columns_order, order_by, separator="\t") | 
|  | .decode("utf-8").encode("UTF-16LE")) | 
|  |  | 
|  | def _ToJSonObj(self, columns_order=None, order_by=()): | 
|  | """Returns an object suitable to be converted to JSON. | 
|  |  | 
|  | Args: | 
|  | columns_order: Optional. A list of all column IDs in the order in which | 
|  | you want them created in the output table. If specified, | 
|  | all column IDs must be present. | 
|  | order_by: Optional. Specifies the name of the column(s) to sort by. | 
|  | Passed as is to _PreparedData(). | 
|  |  | 
|  | Returns: | 
|  | A dictionary object for use by ToJSon or ToJSonResponse. | 
|  | """ | 
|  | if columns_order is None: | 
|  | columns_order = [col["id"] for col in self.__columns] | 
|  | col_dict = dict([(col["id"], col) for col in self.__columns]) | 
|  |  | 
|  | # Creating the column JSON objects | 
|  | col_objs = [] | 
|  | for col_id in columns_order: | 
|  | col_obj = {"id": col_dict[col_id]["id"], | 
|  | "label": col_dict[col_id]["label"], | 
|  | "type": col_dict[col_id]["type"]} | 
|  | if col_dict[col_id]["custom_properties"]: | 
|  | col_obj["p"] = col_dict[col_id]["custom_properties"] | 
|  | col_objs.append(col_obj) | 
|  |  | 
|  | # Creating the rows jsons | 
|  | row_objs = [] | 
|  | for row, cp in self._PreparedData(order_by): | 
|  | cell_objs = [] | 
|  | for col in columns_order: | 
|  | value = self.CoerceValue(row.get(col, None), col_dict[col]["type"]) | 
|  | if value is None: | 
|  | cell_obj = None | 
|  | elif isinstance(value, tuple): | 
|  | cell_obj = {"v": value[0]} | 
|  | if len(value) > 1 and value[1] is not None: | 
|  | cell_obj["f"] = value[1] | 
|  | if len(value) == 3: | 
|  | cell_obj["p"] = value[2] | 
|  | else: | 
|  | cell_obj = {"v": value} | 
|  | cell_objs.append(cell_obj) | 
|  | row_obj = {"c": cell_objs} | 
|  | if cp: | 
|  | row_obj["p"] = cp | 
|  | row_objs.append(row_obj) | 
|  |  | 
|  | json_obj = {"cols": col_objs, "rows": row_objs} | 
|  | if self.custom_properties: | 
|  | json_obj["p"] = self.custom_properties | 
|  |  | 
|  | return json_obj | 
|  |  | 
|  | def ToJSon(self, columns_order=None, order_by=()): | 
|  | """Returns a string that can be used in a JS DataTable constructor. | 
|  |  | 
|  | This method writes a JSON string that can be passed directly into a Google | 
|  | Visualization API DataTable constructor. Use this output if you are | 
|  | hosting the visualization HTML on your site, and want to code the data | 
|  | table in Python. Pass this string into the | 
|  | google.visualization.DataTable constructor, e.g,: | 
|  | ... on my page that hosts my visualization ... | 
|  | google.setOnLoadCallback(drawTable); | 
|  | function drawTable() { | 
|  | var data = new google.visualization.DataTable(_my_JSon_string, 0.6); | 
|  | myTable.draw(data); | 
|  | } | 
|  |  | 
|  | Args: | 
|  | columns_order: Optional. Specifies the order of columns in the | 
|  | output table. Specify a list of all column IDs in the order | 
|  | in which you want the table created. | 
|  | Note that you must list all column IDs in this parameter, | 
|  | if you use it. | 
|  | order_by: Optional. Specifies the name of the column(s) to sort by. | 
|  | Passed as is to _PreparedData(). | 
|  |  | 
|  | Returns: | 
|  | A JSon constructor string to generate a JS DataTable with the data | 
|  | stored in the DataTable object. | 
|  | Example result (the result is without the newlines): | 
|  | {cols: [{id:"a",label:"a",type:"number"}, | 
|  | {id:"b",label:"b",type:"string"}, | 
|  | {id:"c",label:"c",type:"number"}], | 
|  | rows: [{c:[{v:1},{v:"z"},{v:2}]}, c:{[{v:3,f:"3$"},{v:"w"},{v:null}]}], | 
|  | p:    {'foo': 'bar'}} | 
|  |  | 
|  | Raises: | 
|  | DataTableException: The data does not match the type. | 
|  | """ | 
|  |  | 
|  | encoder = DataTableJSONEncoder() | 
|  | return encoder.encode( | 
|  | self._ToJSonObj(columns_order, order_by)).encode("utf-8") | 
|  |  | 
|  | def ToJSonResponse(self, columns_order=None, order_by=(), req_id=0, | 
|  | response_handler="google.visualization.Query.setResponse"): | 
|  | """Writes a table as a JSON response that can be returned as-is to a client. | 
|  |  | 
|  | This method writes a JSON response to return to a client in response to a | 
|  | Google Visualization API query. This string can be processed by the calling | 
|  | page, and is used to deliver a data table to a visualization hosted on | 
|  | a different page. | 
|  |  | 
|  | Args: | 
|  | columns_order: Optional. Passed straight to self.ToJSon(). | 
|  | order_by: Optional. Passed straight to self.ToJSon(). | 
|  | req_id: Optional. The response id, as retrieved by the request. | 
|  | response_handler: Optional. The response handler, as retrieved by the | 
|  | request. | 
|  |  | 
|  | Returns: | 
|  | A JSON response string to be received by JS the visualization Query | 
|  | object. This response would be translated into a DataTable on the | 
|  | client side. | 
|  | Example result (newlines added for readability): | 
|  | google.visualization.Query.setResponse({ | 
|  | 'version':'0.6', 'reqId':'0', 'status':'OK', | 
|  | 'table': {cols: [...], rows: [...]}}); | 
|  |  | 
|  | Note: The URL returning this string can be used as a data source by Google | 
|  | Visualization Gadgets or from JS code. | 
|  | """ | 
|  |  | 
|  | response_obj = { | 
|  | "version": "0.6", | 
|  | "reqId": str(req_id), | 
|  | "table": self._ToJSonObj(columns_order, order_by), | 
|  | "status": "ok" | 
|  | } | 
|  | encoder = DataTableJSONEncoder() | 
|  | return "%s(%s);" % (response_handler, | 
|  | encoder.encode(response_obj).encode("utf-8")) | 
|  |  | 
|  | def ToResponse(self, columns_order=None, order_by=(), tqx=""): | 
|  | """Writes the right response according to the request string passed in tqx. | 
|  |  | 
|  | This method parses the tqx request string (format of which is defined in | 
|  | the documentation for implementing a data source of Google Visualization), | 
|  | and returns the right response according to the request. | 
|  | It parses out the "out" parameter of tqx, calls the relevant response | 
|  | (ToJSonResponse() for "json", ToCsv() for "csv", ToHtml() for "html", | 
|  | ToTsvExcel() for "tsv-excel") and passes the response function the rest of | 
|  | the relevant request keys. | 
|  |  | 
|  | Args: | 
|  | columns_order: Optional. Passed as is to the relevant response function. | 
|  | order_by: Optional. Passed as is to the relevant response function. | 
|  | tqx: Optional. The request string as received by HTTP GET. Should be in | 
|  | the format "key1:value1;key2:value2...". All keys have a default | 
|  | value, so an empty string will just do the default (which is calling | 
|  | ToJSonResponse() with no extra parameters). | 
|  |  | 
|  | Returns: | 
|  | A response string, as returned by the relevant response function. | 
|  |  | 
|  | Raises: | 
|  | DataTableException: One of the parameters passed in tqx is not supported. | 
|  | """ | 
|  | tqx_dict = {} | 
|  | if tqx: | 
|  | tqx_dict = dict(opt.split(":") for opt in tqx.split(";")) | 
|  | if tqx_dict.get("version", "0.6") != "0.6": | 
|  | raise DataTableException( | 
|  | "Version (%s) passed by request is not supported." | 
|  | % tqx_dict["version"]) | 
|  |  | 
|  | if tqx_dict.get("out", "json") == "json": | 
|  | response_handler = tqx_dict.get("responseHandler", | 
|  | "google.visualization.Query.setResponse") | 
|  | return self.ToJSonResponse(columns_order, order_by, | 
|  | req_id=tqx_dict.get("reqId", 0), | 
|  | response_handler=response_handler) | 
|  | elif tqx_dict["out"] == "html": | 
|  | return self.ToHtml(columns_order, order_by) | 
|  | elif tqx_dict["out"] == "csv": | 
|  | return self.ToCsv(columns_order, order_by) | 
|  | elif tqx_dict["out"] == "tsv-excel": | 
|  | return self.ToTsvExcel(columns_order, order_by) | 
|  | else: | 
|  | raise DataTableException( | 
|  | "'out' parameter: '%s' is not supported" % tqx_dict["out"]) |