# Buggy code
def query(  # druid
    self,
    groupby,
    metrics,
    granularity,
    from_dttm,
    to_dttm,
    filter=None,  # noqa
    is_timeseries=True,
    timeseries_limit=None,
    row_limit=None,
    inner_from_dttm=None,
    inner_to_dttm=None,
    extras=None,  # noqa
    select=None,
):  # noqa
    """Runs a query against Druid and returns a dataframe.

    This query interface is common to SqlAlchemy and Druid
    """
    # TODO refactor into using a TBD Query object
    qry_start_dttm = datetime.now()

    inner_from_dttm = inner_from_dttm or from_dttm
    inner_to_dttm = inner_to_dttm or to_dttm

    # add tzinfo to native datetime with config
    from_dttm = from_dttm.replace(tzinfo=config.get("DRUID_TZ"))
    to_dttm = to_dttm.replace(tzinfo=config.get("DRUID_TZ"))

    query_str = ""
    aggregations = {
        m.metric_name: m.json_obj for m in self.metrics if m.metric_name in metrics
    }
    granularity = granularity or "all"
    if granularity != "all":
        granularity = utils.parse_human_timedelta(granularity).total_seconds() * 1000
    if not isinstance(granularity, string_types):
        granularity = {"type": "duration", "duration": granularity}
        origin = extras.get("druid_time_origin")
        if origin:
            dttm = utils.parse_human_datetime(origin)
            granularity["origin"] = dttm.isoformat()

    qry = dict(
        datasource=self.datasource_name,
        dimensions=groupby,
        aggregations=aggregations,
        granularity=granularity,
        intervals=from_dttm.isoformat() + "/" + to_dttm.isoformat(),
    )
    filters = None
    for col, op, eq in filter:
        cond = None
        if op == "==":
            cond = Dimension(col) == eq
        elif op == "!=":
            cond = ~(Dimension(col) == eq)
        elif op in ("in", "not in"):
            fields = []
            splitted = eq.split(",")
            if len(splitted) > 1:
                for s in eq.split(","):
                    s = s.strip()
                    fields.append(Filter.build_filter(Dimension(col) == s))
                cond = Filter(type="or", fields=fields)
            else:
                cond = Dimension(col) == eq
            if op == "not in":
                cond = ~cond
        if filters:
            filters = Filter(
                type="and",
                fields=[Filter.build_filter(cond), Filter.build_filter(filters)],
            )
        else:
            filters = cond

    if filters:
        qry["filter"] = filters

    client = self.cluster.get_pydruid_client()
    orig_filters = filters
    if timeseries_limit and is_timeseries:
        # Limit on the number of timeseries, doing a two-phases query
        pre_qry = deepcopy(qry)
        pre_qry["granularity"] = "all"
        pre_qry["limit_spec"] = {
            "type": "default",
            "limit": timeseries_limit,
            "intervals": (
                inner_from_dttm.isoformat() + "/" + inner_to_dttm.isoformat()
            ),
            "columns": [
                {
                    "dimension": metrics[0] if metrics else self.metrics[0],
                    "direction": "descending",
                }
            ],
        }
        client.groupby(**pre_qry)
        query_str += "// Two phase query\n// Phase 1\n"
        query_str += json.dumps(client.query_dict, indent=2) + "\n"
        query_str += "//\nPhase 2 (built based on phase one's results)\n"
        df = client.export_pandas()
        if df is not None and not df.empty:
            dims = qry["dimensions"]
            filters = []
            for _, row in df.iterrows():
                fields = []
                for dim in dims:
                    f = Filter.build_filter(Dimension(dim) == row[dim])
                    fields.append(f)
                if len(fields) > 1:
                    filt = Filter(type="and", fields=fields)
                    filters.append(Filter.build_filter(filt))
                elif fields:
                    filters.append(fields[0])

            if filters:
                ff = Filter(type="or", fields=filters)
                if not orig_filters:
                    qry["filter"] = ff
                else:
                    qry["filter"] = Filter(
                        type="and",
                        fields=[
                            Filter.build_filter(ff),
                            Filter.build_filter(orig_filters),
                        ],
                    )
            qry["limit_spec"] = None
    if row_limit:
        qry["limit_spec"] = {
            "type": "default",
            "limit": row_limit,
            "columns": [
                {
                    "dimension": metrics[0] if metrics else self.metrics[0],
                    "direction": "descending",
                }
            ],
        }
    client.groupby(**qry)
    query_str += json.dumps(qry, indent=2)
    df = client.export_pandas()
    if df is None or df.size == 0:
        raise Exception("No data was returned.")

    if not is_timeseries and granularity == "all" and "timestamp" in df.columns:
        del df["timestamp"]

    # Reordering columns
    cols = []
    if "timestamp" in df.columns:
        cols += ["timestamp"]
    cols += [col for col in groupby if col in df.columns]
    cols += [col for col in metrics if col in df.columns]
    cols += [col for col in df.columns if col not in cols]
    df = df[cols]
    return QueryResult(df=df, query=query_str, duration=datetime.now() - qry_start_dttm)


# Traceback (TypeError)
because qry['filter'] is a Filter instance

Stack:
File "/usr/lib/python2.7/site-packages/Flask-0.10.1-py2.7.egg/flask/app.py", line 1836, in __call__
return self.wsgi_app(environ, start_response)
File "/usr/lib/python2.7/site-packages/Flask-0.10.1-py2.7.egg/flask/app.py", line 1820, in wsgi_app
response = self.make_response(self.handle_exception(e))
File "/usr/lib/python2.7/site-packages/Flask-0.10.1-py2.7.egg/flask/app.py", line 1403, in handle_exception
reraise(exc_type, exc_value, tb)
File "/usr/lib/python2.7/site-packages/Flask-0.10.1-py2.7.egg/flask/app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "/usr/lib/python2.7/site-packages/Flask-0.10.1-py2.7.egg/flask/app.py", line 1477, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/lib/python2.7/site-packages/Flask-0.10.1-py2.7.egg/flask/app.py", line 1381, in handle_user_exception
Open an interactive python shell in this framereraise(exc_type, exc_value, tb)
File "/usr/lib/python2.7/site-packages/Flask-0.10.1-py2.7.egg/flask/app.py", line 1475, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/lib/python2.7/site-packages/Flask-0.10.1-py2.7.egg/flask/app.py", line 1461, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/usr/lib/python2.7/site-packages/Flask_AppBuilder-1.6.0-py2.7.egg/flask_appbuilder/security/decorators.py", line 26, in wraps
return f(self, *args, **kwargs)
File "/usr/lib/python2.7/site-packages/caravel-0.8.8-py2.7.egg/caravel/models.py", line 1176, in wrapper
return f(*args, **kwargs)
File "/usr/lib/python2.7/site-packages/caravel-0.8.8-py2.7.egg/caravel/views.py", line 514, in explore
raise e

Traceback (most recent call last):
File "/projects/druid/panoramix/caravel/views.py", line 510, in explore
payload = obj.get_json()
File "/projects/druid/panoramix/caravel/viz.py", line 251, in get_json
'data': self.get_data(),
File "/projects/druid/panoramix/caravel/viz.py", line 745, in get_data
df = self.get_df()
File "/projects/druid/panoramix/caravel/viz.py", line 140, in get_df
self.results = self.datasource.query(**query_obj)
File "/projects/druid/panoramix/caravel/models.py", line 1116, in query
query_str += json.dumps(qry, indent=2)
File "/Users/clu/.pyenv/versions/2.7.5/lib/python2.7/json/__init__.py", line 250, in dumps
sort_keys=sort_keys, **kw).encode(obj)
File "/Users/clu/.pyenv/versions/2.7.5/lib/python2.7/json/encoder.py", line 209, in encode
chunks = list(chunks)
File "/Users/clu/.pyenv/versions/2.7.5/lib/python2.7/json/encoder.py", line 434, in _iterencode
for chunk in _iterencode_dict(o, _current_indent_level):
File "/Users/clu/.pyenv/versions/2.7.5/lib/python2.7/json/encoder.py", line 408, in _iterencode_dict
for chunk in chunks:
File "/Users/clu/.pyenv/versions/2.7.5/lib/python2.7/json/encoder.py", line 442, in _iterencode
o = _default(o)
File "/Users/clu/.pyenv/versions/2.7.5/lib/python2.7/json/encoder.py", line 184, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <pydruid.utils.filters.Filter instance at 0x10ae1d128> is not JSON serializable

# Fixed code
def query(  # druid
    self,
    groupby,
    metrics,
    granularity,
    from_dttm,
    to_dttm,
    filter=None,  # noqa
    is_timeseries=True,
    timeseries_limit=None,
    row_limit=None,
    inner_from_dttm=None,
    inner_to_dttm=None,
    extras=None,  # noqa
    select=None,
):  # noqa
    """Runs a query against Druid and returns a dataframe.

    This query interface is common to SqlAlchemy and Druid
    """
    # TODO refactor into using a TBD Query object
    qry_start_dttm = datetime.now()

    inner_from_dttm = inner_from_dttm or from_dttm
    inner_to_dttm = inner_to_dttm or to_dttm

    # add tzinfo to native datetime with config
    from_dttm = from_dttm.replace(tzinfo=config.get("DRUID_TZ"))
    to_dttm = to_dttm.replace(tzinfo=config.get("DRUID_TZ"))

    query_str = ""
    aggregations = {
        m.metric_name: m.json_obj for m in self.metrics if m.metric_name in metrics
    }
    granularity = granularity or "all"
    if granularity != "all":
        granularity = utils.parse_human_timedelta(granularity).total_seconds() * 1000
    if not isinstance(granularity, string_types):
        granularity = {"type": "duration", "duration": granularity}
        origin = extras.get("druid_time_origin")
        if origin:
            dttm = utils.parse_human_datetime(origin)
            granularity["origin"] = dttm.isoformat()

    qry = dict(
        datasource=self.datasource_name,
        dimensions=groupby,
        aggregations=aggregations,
        granularity=granularity,
        intervals=from_dttm.isoformat() + "/" + to_dttm.isoformat(),
    )
    filters = None
    for col, op, eq in filter:
        cond = None
        if op == "==":
            cond = Dimension(col) == eq
        elif op == "!=":
            cond = ~(Dimension(col) == eq)
        elif op in ("in", "not in"):
            fields = []
            splitted = eq.split(",")
            if len(splitted) > 1:
                for s in eq.split(","):
                    s = s.strip()
                    fields.append(Filter.build_filter(Dimension(col) == s))
                cond = Filter(type="or", fields=fields)
            else:
                cond = Dimension(col) == eq
            if op == "not in":
                cond = ~cond
        if filters:
            filters = Filter(
                type="and",
                fields=[Filter.build_filter(cond), Filter.build_filter(filters)],
            )
        else:
            filters = cond

    if filters:
        qry["filter"] = filters

    client = self.cluster.get_pydruid_client()
    orig_filters = filters
    if timeseries_limit and is_timeseries:
        # Limit on the number of timeseries, doing a two-phases query
        pre_qry = deepcopy(qry)
        pre_qry["granularity"] = "all"
        pre_qry["limit_spec"] = {
            "type": "default",
            "limit": timeseries_limit,
            "intervals": (
                inner_from_dttm.isoformat() + "/" + inner_to_dttm.isoformat()
            ),
            "columns": [
                {
                    "dimension": metrics[0] if metrics else self.metrics[0],
                    "direction": "descending",
                }
            ],
        }
        client.groupby(**pre_qry)
        query_str += "// Two phase query\n// Phase 1\n"
        query_str += json.dumps(client.query_dict, indent=2) + "\n"
        query_str += "//\nPhase 2 (built based on phase one's results)\n"
        df = client.export_pandas()
        if df is not None and not df.empty:
            dims = qry["dimensions"]
            filters = []
            for _, row in df.iterrows():
                fields = []
                for dim in dims:
                    f = Filter.build_filter(Dimension(dim) == row[dim])
                    fields.append(f)
                if len(fields) > 1:
                    filt = Filter(type="and", fields=fields)
                    filters.append(Filter.build_filter(filt))
                elif fields:
                    filters.append(fields[0])

            if filters:
                ff = Filter(type="or", fields=filters)
                if not orig_filters:
                    qry["filter"] = ff
                else:
                    qry["filter"] = Filter(
                        type="and",
                        fields=[
                            Filter.build_filter(ff),
                            Filter.build_filter(orig_filters),
                        ],
                    )
            qry["limit_spec"] = None
    if row_limit:
        qry["limit_spec"] = {
            "type": "default",
            "limit": row_limit,
            "columns": [
                {
                    "dimension": metrics[0] if metrics else self.metrics[0],
                    "direction": "descending",
                }
            ],
        }
    client.groupby(**qry)
    query_str += json.dumps(client.query_dict, indent=2)
    df = client.export_pandas()
    if df is None or df.size == 0:
        raise Exception("No data was returned.")

    if not is_timeseries and granularity == "all" and "timestamp" in df.columns:
        del df["timestamp"]

    # Reordering columns
    cols = []
    if "timestamp" in df.columns:
        cols += ["timestamp"]
    cols += [col for col in groupby if col in df.columns]
    cols += [col for col in metrics if col in df.columns]
    cols += [col for col in df.columns if col not in cols]
    df = df[cols]
    return QueryResult(df=df, query=query_str, duration=datetime.now() - qry_start_dttm)