Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion panoptes_client/set_member_subject.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
class SetMemberSubject(PanoptesObject):
_api_slug = 'set_member_subjects'
_link_slug = 'set_member_subjects'
_edit_attributes = ()
_edit_attributes = (
'priority',
)

LinkResolver.register(SetMemberSubject)
LinkResolver.register(SetMemberSubject, 'set_member_subject')
79 changes: 57 additions & 22 deletions panoptes_client/subject.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
from redo import retry
from panoptes_client.panoptes import (
LinkResolver,
ObjectNotSavedException,
Panoptes,
PanoptesAPIException,
PanoptesObject,
)
import mimetypes
from concurrent.futures import ThreadPoolExecutor
from copy import deepcopy
import threading
import requests
import logging
from builtins import str
from panoptes_client.subject_workflow_status import SubjectWorkflowStatus
from panoptes_client.set_member_subject import SetMemberSubject

_OLD_STR_TYPES = (str,)
try:
_OLD_STR_TYPES = _OLD_STR_TYPES + (unicode,)
except NameError:
pass

from builtins import range, str

import logging
import requests
import threading
import time

from copy import deepcopy
from concurrent.futures import ThreadPoolExecutor
import mimetypes

try:
import magic
Expand All @@ -32,14 +38,6 @@
pass
MEDIA_TYPE_DETECTION = 'mimetypes'

from panoptes_client.panoptes import (
LinkResolver,
ObjectNotSavedException,
Panoptes,
PanoptesAPIException,
PanoptesObject,
)
from redo import retry

UPLOAD_RETRY_LIMIT = 5
RETRY_BACKOFF_INTERVAL = 5
Expand All @@ -58,6 +56,7 @@
"application/json",
]


class Subject(PanoptesObject):
_api_slug = 'subjects'
_link_slug = 'subjects'
Expand Down Expand Up @@ -232,7 +231,8 @@ def _detect_media_type(self, media_data=None, manual_mimetype=None):

def _validate_media_type(self, media_type=None):
if media_type not in ALLOWED_MIME_TYPES:
raise UnknownMediaException(f"File type {media_type} is not allowed.")
raise UnknownMediaException(
f"File type {media_type} is not allowed.")

@property
def async_save_result(self):
Expand Down Expand Up @@ -341,7 +341,8 @@ def _add_attached_image(
client = Panoptes.client()

with client:
json_response, _ = self.http_post('{}/attached_images'.format(self.id), json={'media': media_data})
json_response, _ = self.http_post(
'{}/attached_images'.format(self.id), json={'media': media_data})

return json_response['media'][0]['src']

Expand Down Expand Up @@ -369,7 +370,8 @@ def _save_attached_image(self, attached_media, manual_mimetype=None, metadata=No
media_type = None
try:
media_data = f.read()
media_type = self._detect_media_type(media_data, manual_mimetype)
media_type = self._detect_media_type(
media_data, manual_mimetype)
self._validate_media_type(media_type)
finally:
f.close()
Expand Down Expand Up @@ -444,7 +446,8 @@ def save_attached_image(
if async_save:
upload_exec = self._local.save_exec
else:
upload_exec = ThreadPoolExecutor(max_workers=ASYNC_SAVE_THREADS)
upload_exec = ThreadPoolExecutor(
max_workers=ASYNC_SAVE_THREADS)
future_result = upload_exec.submit(
retry,
self._save_attached_image,
Expand All @@ -467,6 +470,38 @@ def save_attached_image(
upload_exec.shutdown(wait=True)
return future_result

def update_priority(self, priority, subject_set_id=None):
"""
Update the priority of this subject in the subject set.

If subject_set_id is not provided, the priority will be updated in all subject sets that this subject belongs to.

- **priority** is an integer value that represents the priority of the subject in the subject set.

Examples::

subject.update_priority(1)
subject.update_priority(2, subject_set_id=1234)
"""

if self.id is None:
raise ObjectNotSavedException

self.metadata['priority'] = priority
self.save()

if subject_set_id is not None:
subject_sets = [subject_set_id]
else:
subject_sets = [s.id for s in self.links.subject_sets]

for ss_id in subject_sets:
sms = next(SetMemberSubject.where(
subject_set_id=ss_id,
subject_id=self.id))
sms.priority = priority
sms.save()


class UnknownMediaException(Exception):
pass
Expand Down
79 changes: 76 additions & 3 deletions panoptes_client/tests/test_subject.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import io
import mimetypes
import unittest
from unittest.mock import patch, mock_open
from unittest.mock import patch, mock_open, MagicMock

from panoptes_client.panoptes import ObjectNotSavedException
from panoptes_client.subject import Subject, UnknownMediaException
import mimetypes


class TestSubject(unittest.TestCase):
Expand Down Expand Up @@ -56,4 +57,76 @@ def test_add_location_invalid_manual_mimetype(self):
data = b"fake data"
fake_file = io.BytesIO(data)
with self.assertRaises(UnknownMediaException):
self.subject.add_location(fake_file, manual_mimetype="application/javascript")
self.subject.add_location(
fake_file, manual_mimetype="application/javascript")

def test_update_priority_requires_saved_subject(self):
with self.assertRaises(ObjectNotSavedException):
self.subject.update_priority(1)

def test_update_priority_updates_priority_for_saved_subject(self):
self.subject.id = 123
set_member_subject_mock = MagicMock()

with patch.object(self.subject, "save") as mock_save:
with patch(
"panoptes_client.subject.SetMemberSubject.where",
return_value=iter([set_member_subject_mock]),
) as mock_where:
self.subject.update_priority(
5,
subject_set_id=456,
)

self.assertEqual(self.subject.metadata["priority"], 5)

mock_save.assert_called_once_with()
mock_where.assert_called_once_with(
subject_set_id=456,
subject_id=123,
)

self.assertEqual(set_member_subject_mock.priority, 5)
set_member_subject_mock.save.assert_called_once_with()

def test_update_priority_updates_all_subject_sets(self):
self.subject.id = 123

subject_set_1 = MagicMock(id=456)
subject_set_2 = MagicMock(id=789)
set_member_subject_1 = MagicMock()
set_member_subject_2 = MagicMock()

with patch.object(self.subject, "save") as mock_save, \
patch(
"panoptes_client.panoptes.LinkResolver.__getattr__",
return_value=[subject_set_1, subject_set_2],
), \
patch(
"panoptes_client.subject.SetMemberSubject.where",
side_effect=[
iter([set_member_subject_1]),
iter([set_member_subject_2]),
],
) as mock_where:
self.subject.update_priority(5)

self.assertEqual(self.subject.metadata["priority"], 5)

mock_save.assert_called_once_with()

self.assertEqual(mock_where.call_count, 2)
mock_where.assert_any_call(
subject_set_id=456,
subject_id=123,
)
mock_where.assert_any_call(
subject_set_id=789,
subject_id=123,
)

self.assertEqual(set_member_subject_1.priority, 5)
set_member_subject_1.save.assert_called_once_with()

self.assertEqual(set_member_subject_2.priority, 5)
set_member_subject_2.save.assert_called_once_with()
Loading