"""An interface for creating and retrieving Matchlight projects."""
from __future__ import absolute_import
import datetime
try:
import collections.abc as collections_abc
except ImportError:
import collections as collections_abc
import json
import six
import matchlight.error
import matchlight.utils
[docs]class Project(object):
"""A Matchlight Fingerprint Monitoring Project.
Attributes:
name (:obj:`str`): The project name.
project_type (:obj:`str`): The project type.
upload_token (:obj:`str`): The project upload token.
last_date_modified (:obj:`int`): The Unix timestamp of the last
modification.
number_of_records (:obj:`int`): The number of total records in
the project.
number_of_unseen_alerts (:obj:`int`): The number of unread
alerts.
"""
def __init__(self, name, project_type, upload_token,
last_date_modified, number_of_records,
number_of_unseen_alerts):
"""Initializes a new project.
Args:
name (:obj:`str`): The project name.
project_type (:obj:`str`): The project type.
upload_token (:obj:`str`): The project upload token.
last_date_modified (:obj:`int`): The Unix timestamp of the
last modification.
number_of_records (:obj:`int`): The number of total records
in the project.
number_of_unseen_alerts (:obj:`int`): The number of unread
alerts.
"""
self.name = name
self.project_type = project_type
self.upload_token = upload_token
self.last_date_modified = last_date_modified
self.number_of_records = number_of_records
self.number_of_unseen_alerts = number_of_unseen_alerts
[docs] @classmethod
def from_mapping(cls, mapping):
"""Creates a new project instance from the given mapping."""
return cls(
name=mapping['project_name'],
project_type=mapping['project_type'],
upload_token=mapping['project_upload_token'],
last_date_modified=mapping['last_date_modified'],
number_of_records=mapping['number_of_records'],
number_of_unseen_alerts=mapping['number_of_unseen_alerts'],
)
@property
def last_modified(self):
""":class:`datetime.datetime`: The last modified timestamp."""
return datetime.datetime.fromtimestamp(self.last_date_modified)
@property
def details(self):
""":obj:`dict`: Returns the project details as a mapping."""
return {
'name': self.name,
'project_type': self.project_type,
'upload_token': self.upload_token,
'last_date_modified': self.last_date_modified,
'number_of_records': self.number_of_records,
'number_of_unseen_alerts': self.number_of_unseen_alerts,
}
def __repr__(self): # pragma: no cover
return '<Project(name="{}", project_type="{}")>'.format(
self.name, self.project_type)
[docs]class ProjectMethods(collections_abc.Iterable):
"""Provides methods for interfacing with the feeds API.
Examples:
Get project from upload token::
>>> ml.projects.get('3ef85448c-d244-431e-a207-cf8d37ae3bfe')
<Project(name='Customer Database May 2016',
project_type='pii')>
Filter on project types::
>>> ml.projects.filter(project_type='pii')
[<Project(name='...', project_type='pii'),
<Project(name='...', project_type='pii'),
<Project(name='...', project_type='pii')]
>>> ml.projects.filter()
[<Project(name='...', project_type='pii'),
<Project(name='...', project_type='document'),
<Project(name='...', project_type='source_code')]
Create a new project::
>>> project = ml.projects.add(
... name='Super secret algorithm',
... project_type='source_code')
>>> project
<Project(name='Super Secret Algorithm', type='source_code')>
Edit a project::
>>> project = ml.projects.edit(project,
... 'Updated Super Secret Algorithm')
>>> project
<Project(name='Updated Super Secret Algorithm',
type='source_code'>
Delete a project::
>>> ml.projects.delete(project)
>>> ml.projects.get(project.upload_token)
None
Get project details::
>>> executives = ml.projects.add("Executive List 2016", "pii")
>>> executives.details
{'last_date_modified': 1472671877,
'number_of_records': 0,
'number_of_unseen_alerts': 0,
'name': 'Executive List 2016',
'project_type': 'pii',
'upload_token': 'a1c7140a-17e5-4016-8f0a-ef4aa87619ce'}
"""
def __init__(self, ml_connection): # noqa: D205,D400
"""Initializes a project interface with the given Matchlight
connection.
Args:
ml_connection (:class:`~.Connection`): A Matchlight
connection instance.
"""
self.conn = ml_connection
[docs] def all(self):
"""Returns all projects associated with the account."""
return self.filter()
[docs] def add(self, name, project_type):
"""Creates a new project or group.
Arguments:
name (:obj:`str`): The name of the project to be created.
project_type (:obj:`str`): The type of project to be
created.
Returns:
:class:`~.Project` : Created project with upload token.
"""
data = json.dumps({'name': name, 'type': project_type})
r = self.conn.request('/project/add', data=data)
return Project.from_mapping(r.json()['data'])
[docs] def delete(self, project):
"""Deletes a project and all associated records.
Args:
project (:class:`~.Project` or :obj:`str`): The project
object or upload token to be deleted.
"""
if isinstance(project, six.string_types):
upload_token = project
else:
upload_token = project.upload_token
self.conn.request('/project/{upload_token}/delete'.format(
upload_token=upload_token), data='{}')
[docs] def edit(self, project, updated_name):
"""Renames a project.
Arguments:
project (:class:`~.Project` or :obj:`str`): A project
instance or upload token.
updated_name (:obj:`str`): New project name.
Returns:
:class:`~.Project`: Updated project instance with new name.
Note that this method mutates any project instances passed.
"""
if not isinstance(project, Project):
project = self.get(project)
data = json.dumps({'name': updated_name})
self.conn.request('/project/{}/edit'.format(
project.upload_token), data=data)
project.name = updated_name
return project
[docs] def filter(self, project_type=None):
"""Returns a list of projects associated with the account.
Providing an optional **project_type** keyword argument will
only return projects of the specified type: ``source_code``,
``document`` or ``pii``.
Arguments:
project_type (:obj:`str`, optional): The project type to
filter on. If not provided or ``None``, returns all
projects.
Returns:
list of :class:`~.Project`: List of filtered projects.
"""
response = self.conn.request('/projects', params={
'project_type': project_type})
projects = []
for payload in response.json().get('data', []):
if project_type and payload['project_type'] != project_type:
continue
projects.append(Project.from_mapping(payload))
return projects
[docs] def get(self, upload_token):
"""Returns a project by the given upload token.
Args:
upload_token (:obj:`str`): The project upload token.
Returns:
:class: `~.Project`: A Matchlight project.
"""
try:
response = self.conn.request('/project/{}'.format(upload_token))
return Project.from_mapping(response.json())
# for consistency since records.get() returns None if not found.
except matchlight.error.APIError as err:
if err.args[0] == 404:
return
else:
raise
def __iter__(self):
return (project for project in self.filter())