From 7ffdd517c90ba19ec6128454ea89c89802ec2de0 Mon Sep 17 00:00:00 2001 From: yuenmichelle1 Date: Wed, 26 Aug 2026 10:46:54 -0500 Subject: [PATCH 1/3] Update priority of Subject within Subject Set --- panoptes_client/set_member_subject.py | 4 +++- panoptes_client/subject.py | 33 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/panoptes_client/set_member_subject.py b/panoptes_client/set_member_subject.py index 298e52fa..c7ab2db5 100644 --- a/panoptes_client/set_member_subject.py +++ b/panoptes_client/set_member_subject.py @@ -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') diff --git a/panoptes_client/subject.py b/panoptes_client/subject.py index db155626..86e02f8b 100644 --- a/panoptes_client/subject.py +++ b/panoptes_client/subject.py @@ -1,4 +1,5 @@ from panoptes_client.subject_workflow_status import SubjectWorkflowStatus +from panoptes_client.set_member_subject import SetMemberSubject _OLD_STR_TYPES = (str,) try: @@ -466,6 +467,38 @@ def save_attached_image( # Shuts down and waits for the task if this isn't being used in a `async_saves` block 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): From fe70f6846b15972d7334e4cc077501b6374edb13 Mon Sep 17 00:00:00 2001 From: yuenmichelle1 Date: Thu, 27 Aug 2026 16:53:21 -0500 Subject: [PATCH 2/3] update tests for update_priority tests --- panoptes_client/tests/test_subject.py | 80 +++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/panoptes_client/tests/test_subject.py b/panoptes_client/tests/test_subject.py index 15b5e83e..a8c99584 100644 --- a/panoptes_client/tests/test_subject.py +++ b/panoptes_client/tests/test_subject.py @@ -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): @@ -39,7 +40,7 @@ def test_add_location_magic_detection(self, mock_magic): self.assertIn("locations", self.subject.modified_attributes) mock_magic.from_buffer.assert_called_with(data, mime=True) - @patch.object(mimetypes, 'guess_type', return_value=("image/jpeg", None)) + @patch("panoptes_client.subject.mimetypes.guess_type", return_value=("image/jpeg", None)) def test_add_location_mimetypes_detection(self, mock_guess_type): import panoptes_client.subject as subject_module subject_module.MEDIA_TYPE_DETECTION = 'mimetypes' @@ -56,4 +57,75 @@ 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") \ No newline at end of file + 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() From e403360d1e87ab063278fb33e4a20dad51648d0c Mon Sep 17 00:00:00 2001 From: yuenmichelle1 Date: Fri, 28 Aug 2026 11:00:28 -0500 Subject: [PATCH 3/3] remove trailing whitespaces --- panoptes_client/subject.py | 2 +- panoptes_client/tests/test_subject.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/panoptes_client/subject.py b/panoptes_client/subject.py index 86e02f8b..1a9f23b0 100644 --- a/panoptes_client/subject.py +++ b/panoptes_client/subject.py @@ -467,7 +467,7 @@ def save_attached_image( # Shuts down and waits for the task if this isn't being used in a `async_saves` block 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. diff --git a/panoptes_client/tests/test_subject.py b/panoptes_client/tests/test_subject.py index a8c99584..c1c1a8ac 100644 --- a/panoptes_client/tests/test_subject.py +++ b/panoptes_client/tests/test_subject.py @@ -58,7 +58,7 @@ def test_add_location_invalid_manual_mimetype(self): fake_file = io.BytesIO(data) with self.assertRaises(UnknownMediaException): 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) @@ -87,7 +87,7 @@ def test_update_priority_updates_priority_for_saved_subject(self): 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