libmozdata/socorro.py (173 lines of code) (raw):
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import six
from . import config, utils
from .connection import Connection, Query
class Socorro(Connection):
"""Socorro connection: https://crash-stats.mozilla.org"""
CRASH_STATS_URL = config.get("Socorro", "URL", "https://crash-stats.mozilla.org")
API_URL = CRASH_STATS_URL + "/api"
TOKEN = config.get("Socorro", "token", "")
def __init__(self, queries, **kwargs):
"""Constructor
Args:
queries (List[Query]): queries to pass to Socorro
"""
super(Socorro, self).__init__(self.CRASH_STATS_URL, queries=queries, **kwargs)
def get_header(self):
header = super(Socorro, self).get_header()
header["Auth-Token"] = self.get_apikey()
return header
class SuperSearch(Socorro):
"""SuperSearch: https://crash-stats.mozilla.org/api/#SuperSearch"""
URL = Socorro.API_URL + "/SuperSearch/"
WEB_URL = Socorro.CRASH_STATS_URL + "/search/"
def __init__(
self, params=None, handler=None, handlerdata=None, queries=None, **kwargs
):
"""Constructor
Args:
params (Optional[dict]): the params for the query
handler (Optional[function]): handler to use with the result of the query
handlerdata (Optional): data used in second argument of the handler
queries (Optional[List[Query]]): queries to execute
"""
if queries is not None:
super(SuperSearch, self).__init__(queries, **kwargs)
else:
if self.__has_deprecated_unredacted_params(params):
raise ValueError(
"Requesting PII data using the `SuperSearch` class is not "
"supported anymore. Please use `SuperSearchUnredacted` instead."
)
super(SuperSearch, self).__init__(
Query(self.URL, params, handler, handlerdata), **kwargs
)
def __has_deprecated_unredacted_params(self, params):
"""Check if the params is requesting PII data that we used to
automatically support retrieving it by redirecting the request to the
unredacted endpoint (i.e., SuperSearchUnredacted).
"""
if not isinstance(params, dict):
# This is a workaround to avoid crashing when the params values is a
# list of params. We could instead of this, check each item in the
# list, but it's not worth it since this method is just for backward
# compatibility.
return False
unredacted = False
if "_facets" in params:
facets = params["_facets"]
if "url" in facets or "email" in facets:
unredacted = True
if not unredacted and "_columns" in params:
columns = params["_columns"]
if "url" in columns or "email" in columns:
unredacted = True
if not unredacted:
for k, v in params.items():
if (
"url" in k
or "email" in k
or (
(isinstance(v, list) or isinstance(v, six.string_types))
and ("url" in v or "email" in v)
)
):
unredacted = True
return unredacted
@staticmethod
def get_link(params):
return utils.get_url(SuperSearch.WEB_URL) + utils.get_params_for_url(params)
@staticmethod
def get_search_date(start, end=None):
"""Get a search date list for [start, end[ (end can be in the future)
Args:
start (str): start date in 'YYYY-mm-dd' format or 'today'
end (str): start date in 'YYYY-mm-dd' format or 'today'
Returns:
List(str): containing acceptable interval for Socorro.SuperSearch
"""
_start = utils.get_date(start)
if end:
_end = utils.get_date_ymd(end)
today = utils.get_date_ymd("today")
if _end > today:
search_date = [">=" + _start]
else:
search_date = [">=" + _start, "<" + utils.get_date_str(_end)]
else:
search_date = [">=" + _start]
return search_date
class SuperSearchUnredacted(SuperSearch):
"""SuperSearchUnredacted: https://crash-stats.mozilla.org/api/#SuperSearchUnredacted"""
URL = Socorro.API_URL + "/SuperSearchUnredacted/"
class ProcessedCrash(Socorro):
"""ProcessedCrash: https://crash-stats.mozilla.org/api/#ProcessedCrash"""
URL = Socorro.API_URL + "/ProcessedCrash/"
def __init__(
self, params=None, handler=None, handlerdata=None, queries=None, **kwargs
):
"""Constructor
Args:
params (Optional[dict]): the params for the query
handler (Optional[function]): handler to use with the result of the query
handlerdata (Optional): data used in second argument of the handler
queries (Optional[List[Query]]): queries to execute
"""
if queries:
super(ProcessedCrash, self).__init__(queries, **kwargs)
else:
super(ProcessedCrash, self).__init__(
Query(ProcessedCrash.URL, params, handler, handlerdata), **kwargs
)
@staticmethod
def default_handler(json, data):
"""Default handler
Args:
json (dict): json
data (dict): dictionary to update with data
"""
data.update(json)
@staticmethod
def get_processed(crashids):
"""Get processed crashes
Args:
crashids (Optional[list[str]]): the crash ids
Returns:
dict: the processed crashes
"""
data = {}
__base = {"crash_id": None, "datatype": "processed"}
if isinstance(crashids, six.string_types):
__base["crash_id"] = crashids
_dict = {}
data[crashids] = _dict
ProcessedCrash(
params=__base, handler=ProcessedCrash.default_handler, handlerdata=_dict
).wait()
else:
queries = []
for crashid in crashids:
cparams = __base.copy()
cparams["crash_id"] = crashid
_dict = {}
data[crashid] = _dict
queries.append(
Query(
ProcessedCrash.URL,
cparams,
ProcessedCrash.default_handler,
_dict,
)
)
ProcessedCrash(queries=queries).wait()
return data
class Bugs(Socorro):
"""Bugs: https://crash-stats.mozilla.org/api/#Bugs"""
URL = Socorro.API_URL + "/Bugs/"
def __init__(
self, params=None, handler=None, handlerdata=None, queries=None, **kwargs
):
"""Constructor
Args:
params (Optional[dict]): the params for the query
handler (Optional[function]): handler to use with the result of the query
handlerdata (Optional): data used in second argument of the handler
queries (Optional[List[Query]]): queries to execute
"""
if queries:
super(Bugs, self).__init__(queries, **kwargs)
else:
super(Bugs, self).__init__(
Query(Bugs.URL, params, handler, handlerdata), **kwargs
)
@staticmethod
def get_bugs(signatures):
"""Get signatures bugs
Args:
signatures (List[str]): the signatures
Returns:
dict: the bugs for each signature
"""
def default_handler(json, data):
if json["total"]:
for hit in json["hits"]:
signature = hit["signature"]
if signature in data:
data[signature].add(hit["id"])
if isinstance(signatures, six.string_types):
data = {signatures: set()}
Bugs(
params={"signatures": signatures},
handler=default_handler,
handlerdata=data,
).wait()
else:
data = {s: set() for s in signatures}
queries = []
for sgns in Connection.chunks(signatures, 10):
queries.append(
Query(Bugs.URL, {"signatures": sgns}, default_handler, data)
)
Bugs(queries=queries).wait()
for k, v in data.items():
data[k] = list(v)
return data
class SignatureFirstDate(Socorro):
"""SignatureFirstDate: https://crash-stats.mozilla.org/api/#SignatureFirstDate"""
URL = Socorro.API_URL + "/SignatureFirstDate/"
def __init__(
self, params=None, handler=None, handlerdata=None, queries=None, **kwargs
):
"""Constructor
Args:
params (Optional[dict]): the params for the query
handler (Optional[function]): handler to use with the result of the query
handlerdata (Optional): data used in second argument of the handler
queries (Optional[List[Query]]): queries to execute
"""
if queries:
super(SignatureFirstDate, self).__init__(queries, **kwargs)
else:
super(SignatureFirstDate, self).__init__(
Query(SignatureFirstDate.URL, params, handler, handlerdata), **kwargs
)
@staticmethod
def get_signatures(signatures):
"""Get the first date and build id for the specified signatures.
Args:
signatures (List[str]): signatures to check.
Returns:
dict: the first date for each signature.
"""
def default_handler(json, data):
data.extend(json["hits"])
data = []
SignatureFirstDate(
params={"signatures": signatures},
handler=default_handler,
handlerdata=data,
).wait()
return data