Source code for matchlight.alert

"""An interface for creating and retrieving alerts in Matchlight."""
from __future__ import absolute_import

import calendar
import datetime
import json

import matchlight.error


__all__ = (
    'Alert',
    'AlertMethods',
)


[docs]class Alert(object): """Represents an alert. Attributes: id (:obj:`str`): A 128-bit UUID. number (:obj:`int`): The account specific alert number. type (:obj:`str`): The type of the associated Record. url (:obj:`str`): The url where the match was found. url_metadata (:obj:`dict`): additional information about the url. ctime (:obj:`int`): A Unix timestamp of the alert creation timestamp. mtime (:obj:`int`): A Unix timestamp of the alert last modification date timestamp. seen (:obj:`bool`): User specific flag. archived (:obj:`bool`): User specific flag. upload_token (:obj:`str`): The upload_token of the associated Project. details (:obj:`dict`): Additional information about the Alert. project_name (:obj:`str`): The name of the associated Project record_name (:obj:`str`): The name of the associated Record. """ def __init__(self, id, number, type, url, url_metadata, ctime, mtime, seen, archived, upload_token, details, project_name, record_name): """Initializes a new alert. Args: id (:obj:`str`): A 128-bit UUID. number (:obj:`int`): The account specific alert number. type (:obj:`str`): The type of the associated Record. url (:obj:`str`): The url where the match was found. ctime (:obj:`int`): A Unix timestamp of the alert creation timestamp. mtime (:obj:`int`): A Unix timestamp of the alert last modification date timestamp. seen (:obj:`bool`): User specific flag. archived (:obj:`bool`): User specific flag. upload_token (:obj:`str`): The upload_token of the associated record. details (:obj:`dict`): details about the Alert. project_name (:obj:`str`): The name of the associated Project record_name (:obj:`str`): The name of the associated Record. """ self.id = id self.number = number self.type = type self.url = url self.url_metadata = url_metadata self.ctime = ctime self.mtime = mtime self.seen = seen self.archived = archived self.upload_token = upload_token self.details = details self.project_name = project_name self.record_name = record_name
[docs] @classmethod def from_mapping(cls, mapping): """Creates a new alert instance from the given mapping.""" return cls( id=mapping['id'], number=mapping['alert_number'], type=mapping['type'], url=mapping['url'], url_metadata=mapping['url_metadata'], ctime=mapping['ctime'], mtime=mapping['mtime'], seen=True if mapping['seen'] == 'true' else False, archived=True if mapping['archived'] == 'true' else False, upload_token=mapping['upload_token'], details=mapping['details'], project_name=mapping['project_name'], record_name=mapping['asset_name'] )
@property def last_modified(self): """:class:`datetime.datetime`: The last modified timestamp.""" if self.mtime is None: return None return datetime.datetime.fromtimestamp(self.mtime) @property def date(self): """:class:`datetime.datetime`: The date created timestamp.""" if self.ctime is None: return None return datetime.datetime.fromtimestamp(self.ctime) @property def score(self): """:obj:`int`: Represents how much of the record appeared on the page. Scores range from 1 to 800, with 800 representing that the entire record was found on the page. PII records will always have a score of 800. """ if self.type == 'pii': return 800 if self.type == 'document': return self.details['document'].get('score', None) if self.type == 'source_code': return self.details['source_code'].get('score', None) @property def fields(self): """:obj:`list`: PII records will match on one or more 'fields'.""" if self.type == 'pii': return self.details['pii'].get('fields', []) return None def __repr__(self): # pragma: no cover return '<Alert(number="{}", id="{}")>'.format( self.number, self.id )
[docs]class AlertMethods(object): """Provides methods for interfacing with the alerts API.""" def __init__(self, ml_connection): # noqa: D205,D400 """Initializes an alerts interface with the given Matchlight connection. Args: ml_connection (:class:`~.Connection`): A Matchlight connection instance. """ self.conn = ml_connection
[docs] def filter(self, limit, seen=None, archived=None, project=None, record=None, last_modified=None, last_alert=None): """Returns a list of alerts. Providing a **limit** keyword argument will limit the number of alerts returned. The request may time out if this is set too high, a limit of 50 is recomended to avoid timeouts. Providing an optional **seen** keyword argument will only return alerts that match that property Providing an optional **archived** keyword argument will only return alerts that match that property Providing an optional **project** keyword argument will only return alerts that are associated with a specific project. Providing an optional **record** keyword argument will only return alerts that are associated with a specific record. Providing an optional **last_modified** keyword argument will only return alerts with a last_modifed less than the argument. Providing an optional **last_alert** keyword argument will only return results after this Alert Examples: Request all unseen alerts:: >>> ml.alerts.filter(seen=False, limit=50) [<Alert(number="1024", id="625a732ad0f247beab18595z951c2088a3")>, Alert(number="1025", id="f9427dd5a24d4a98b2069004g04c2977")] Request all alerts for a project:: >>> my_project <Project(name="Super Secret Algorithm", type="source_code")> >>> ml.alerts.filter(project=my_project, limit=50) [<Alert(number="1024", id="625a732ad0f247beab18595z951c2088a3")>, Alert(number="1025", id="f9427dd5a24d4a98b2069004g04c2977")] Request sets of alerts using pagination:: >>> ml.alerts.filter(limit=50) [<Alert(number="1027", id="625a732ad247beab18595z951c2088a3")>, Alert(number="1026", id="f9427dd5a24d4a98b2069004g04c2977")... >>> ml.alerts.filter(limit=50, last_alert=50) [<Alert(number="977", id="59d5a791g8d4436aaffe64e4b15474a5")>, Alert(number="976", id="6b1001aaec5a48f19d17171169eebb56")... Args: limit (:obj:`int`): Don't return more than this number of alerts. seen (:obj:`bool`, optional): archived (:obj:`bool`, optional): project (:class:`~.Project`, optional): a project object. Defaults to all projects if not specified. record (:class:`~.Record`, optional): a record object. Defaults to all projects if not specified. last_modified (:obj:`datetime`, optional): last_alert (:obj:`int`): Only return Alerts after this one. Returns: :obj:`list` of :class:`~.Alert`: List of alerts that are associated with a project. """ if seen is not None: seen_int = 1 if seen is True else 0 else: seen_int = None if archived is not None: archived_int = 1 if archived is True else 0 else: archived_int = None if project is not None: upload_token = project.upload_token else: upload_token = None if record is not None: record_id = record.id else: record_id = None if last_modified is not None: mtime = calendar.timegm(last_modified.timetuple()) else: mtime = None response = self.conn.request( '/alerts', params={ 'limit': limit, 'seen': seen_int, 'archived': archived_int, 'upload_token_filter': upload_token, 'record_id_filter': record_id, 'mtime': mtime, 'last_alert': last_alert } ) alerts = [] for payload in response.json().get('alerts', []): alerts.append(Alert.from_mapping(payload)) return alerts
[docs] def edit(self, alert_id, seen=None, archived=None): """Edits an alert. Example: Archive an alert:: >>> alert <Alert(number=1024, id="0760570a2c4a4ea68d526f58bab46cbd")> >>> ml.alerts.edit(alert, archived=True) { 'seen': True, 'archived': True } Arguments: alert (:obj:`str`): An alert id. seen (:obj:`bool`, optional): archived (:obj:`bool`, optional): Returns: :obj:`dict`: Updated alert metadata. """ if isinstance(alert_id, Alert): alert_id = alert_id.id data = {} if seen is not None: data['seen'] = seen if archived is not None: data['archived'] = archived response = self.conn.request( '/alert/{}/edit'.format(alert_id), data=json.dumps(data) ) response = response.json() return { 'seen': response['seen'], 'archived': response['archived'] }
[docs] def get_details(self, alert_id): """Returns details of an alert by the given alert ID. Args: alert_id (:obj:`str`): The alert identifier. Returns: :obj:`dict`: map of the alert details. """ if isinstance(alert_id, Alert): alert_id = alert_id.id try: response = self.conn.request('/alert/{}/details'.format(alert_id)) return response.json() except matchlight.error.APIError as err: if err.args[0] == 404: return else: raise