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
12 changes: 8 additions & 4 deletions docs/guides/extensions/curator/metadata_curation.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ record_set, curation_task, data_grid = create_record_based_metadata_task(
upsert_keys=["StudyKey"], # Fields that uniquely identify records
instructions="Complete all required fields according to the schema. Use StudyKey to link records to your data files.",
schema_uri=schema_uri, # Schema found in Step 2
bind_schema_to_record_set=True
bind_schema_to_record_set=True,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really great. I have one final small request: can we add a section up top on prerequisites for the fact that a Synapse team must exist now for collaboration on a grid

assignee_principal_id="123456" # Optional: Assign to a user or team
)

print(f"Created RecordSet: {record_set.id}")
Expand All @@ -106,7 +107,8 @@ entity_view_id, task_id = create_file_based_metadata_task(
instructions="Annotate each file with metadata according to the schema requirements.",
attach_wiki=False, # Creates a wiki in the folder with the entity view (Defaults to False)
entity_view_name="Animal Study Files View",
schema_uri=schema_uri # Schema found in Step 2
schema_uri=schema_uri, # Schema found in Step 2
assignee_principal_id="123456" # Optional: Assign to a user or team
)

print(f"Created EntityView: {entity_view_id}")
Expand Down Expand Up @@ -156,7 +158,8 @@ record_set, curation_task, data_grid = create_record_based_metadata_task(
upsert_keys=["StudyKey"],
instructions="Complete metadata for all study animals using StudyKey to link records to data files.",
schema_uri=schema_uri,
bind_schema_to_record_set=True
bind_schema_to_record_set=True,
assignee_principal_id="123456" # Optional: Assign to a user or team
)

print(f"Record-based workflow created:")
Expand All @@ -171,7 +174,8 @@ entity_view_id, task_id = create_file_based_metadata_task(
instructions="Annotate each file with complete metadata according to schema.",
attach_wiki=True,
entity_view_name="Animal Study Files View",
schema_uri=schema_uri
schema_uri=schema_uri,
assignee_principal_id="123456" # Optional: Assign to a user or team
)

print(f"File-based workflow created:")
Expand Down
16 changes: 14 additions & 2 deletions synapseclient/extensions/curator/file_based_metadata_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
in Synapse, including EntityView creation, CurationTask setup, and Wiki attachment.
"""

from typing import Any, Optional, Tuple
from typing import Any, Optional, Tuple, Union

from synapseclient import Synapse # type: ignore
from synapseclient import Wiki # type: ignore
Expand Down Expand Up @@ -298,6 +298,7 @@ def create_file_based_metadata_task(
entity_view_name: str = "JSON Schema view",
schema_uri: Optional[str] = None,
enable_derived_annotations: bool = False,
assignee_principal_id: Optional[Union[str, int]] = None,
*,
synapse_client: Optional[Synapse] = None,
) -> Tuple[str, str]:
Expand All @@ -322,7 +323,8 @@ def create_file_based_metadata_task(
instructions="Please curate this metadata according to the schema requirements",
attach_wiki=False,
entity_view_name="Biospecimen Metadata View",
schema_uri="sage.schemas.v2571-amp.Biospecimen.schema-0.0.1"
schema_uri="sage.schemas.v2571-amp.Biospecimen.schema-0.0.1",
assignee_principal_id=123456 # Optional: Assign to a user or team (can be str or int)
)
```
Expand All @@ -338,6 +340,11 @@ def create_file_based_metadata_task(
the schema will be bound to the folder before creating the entity view.
(e.g., 'sage.schemas.v2571-amp.Biospecimen.schema-0.0.1')
enable_derived_annotations: If true, enable derived annotations. Defaults to False.
assignee_principal_id: The principal ID of the user or team to assign to this
curation task. Can be provided as either a string or an integer. If None
(default), the task will be unassigned. For metadata tasks, this determines
the owner of the grid session. Team members can all join grid sessions owned
by their team, while user-owned grid sessions are restricted to that user only.
synapse_client: If not passed in and caching was not disabled by
`Synapse.allow_client_caching(False)` this will use the last created
instance from the Synapse class constructor.
Expand Down Expand Up @@ -445,6 +452,11 @@ def create_file_based_metadata_task(
data_type=task_datatype,
project_id=project.id,
instructions=instructions,
assignee_principal_id=(
str(assignee_principal_id)
if assignee_principal_id is not None
else None
),
task_properties=FileBasedMetadataTaskProperties(
upload_folder_id=folder_id,
file_view_id=entity_view_id,
Expand Down
16 changes: 14 additions & 2 deletions synapseclient/extensions/curator/record_based_metadata_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
in Synapse, including RecordSet creation, CurationTask setup, and Grid view initialization.
"""
import tempfile
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict, List, Optional, Tuple, Union

from synapseclient import Synapse
from synapseclient.core.typing_utils import DataFrame as DATA_FRAME_TYPE
Expand Down Expand Up @@ -109,6 +109,7 @@ def create_record_based_metadata_task(
schema_uri: str,
bind_schema_to_record_set: bool = True,
enable_derived_annotations: bool = False,
assignee_principal_id: Optional[Union[str, int]] = None,
*,
synapse_client: Optional[Synapse] = None,
) -> Tuple[RecordSet, CurationTask, Grid]:
Expand Down Expand Up @@ -148,7 +149,8 @@ def create_record_based_metadata_task(
curation_task_name="BiospecimenMetadataTemplate",
upsert_keys=["specimenID"],
instructions="Please curate this metadata according to the schema requirements",
schema_uri="schema-org-schema.name.schema-v1.0.0"
schema_uri="schema-org-schema.name.schema-v1.0.0",
assignee_principal_id=123456 # Optional: Assign to a user or team (can be str or int)
)
```
Expand All @@ -167,6 +169,11 @@ def create_record_based_metadata_task(
bind_schema_to_record_set: Whether to bind the given schema to the RecordSet
(default: True).
enable_derived_annotations: If true, enable derived annotations. Defaults to False.
assignee_principal_id: The principal ID of the user or team to assign to this
curation task. Can be provided as either a string or an integer. If None
(default), the task will be unassigned. For metadata tasks, this determines
the owner of the grid session. Team members can all join grid sessions owned
by their team, while user-owned grid sessions are restricted to that user only.
synapse_client: If not passed in and caching was not disabled by
`Synapse.allow_client_caching(False)` this will use the last created
instance from the Synapse class constructor.
Expand Down Expand Up @@ -244,6 +251,11 @@ def create_record_based_metadata_task(
data_type=curation_task_name,
project_id=project_id,
instructions=instructions,
assignee_principal_id=(
str(assignee_principal_id)
if assignee_principal_id is not None
else None
),
task_properties=RecordBasedMetadataTaskProperties(
record_set_id=record_set_id,
),
Expand Down
7 changes: 7 additions & 0 deletions synapseclient/models/curation.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,11 @@ class CurationTask(CurationTaskSynchronousProtocol):
modified_by: Optional[str] = None
"""(Read Only) The ID of the user that last modified this task"""

assignee_principal_id: Optional[str] = None
"""The principal ID of the user or team assigned to this task. Null if unassigned. For metadata
tasks, determines the owner of the grid session. Team members can all join grid sessions
owned by their team, while user-owned grid sessions are restricted to that user only."""

_last_persistent_instance: Optional["CurationTask"] = field(
default=None, repr=False, compare=False
)
Expand Down Expand Up @@ -510,6 +515,7 @@ def fill_from_dict(
self.modified_on = synapse_response.get("modifiedOn", None)
self.created_by = synapse_response.get("createdBy", None)
self.modified_by = synapse_response.get("modifiedBy", None)
self.assignee_principal_id = synapse_response.get("assigneePrincipalId", None)

task_properties_dict = synapse_response.get("taskProperties", None)
if task_properties_dict:
Expand All @@ -536,6 +542,7 @@ def to_synapse_request(self) -> Dict[str, Any]:
request_dict["modifiedOn"] = self.modified_on
request_dict["createdBy"] = self.created_by
request_dict["modifiedBy"] = self.modified_by
request_dict["assigneePrincipalId"] = self.assignee_principal_id

if self.task_properties is not None:
request_dict["taskProperties"] = self.task_properties.to_synapse_request()
Expand Down
5 changes: 4 additions & 1 deletion tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,10 @@ async def _cleanup(syn: Synapse, items):
else:
print("Error cleaning up entity: " + str(ex))
else:
sys.stderr.write("Don't know how to clean: %s" % str(item))
sys.stderr.write(
"Don't know how to clean: %s (type: %s)"
% (str(item), type(item).__name__)
)


active_span_processors = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
Project,
RecordBasedMetadataTaskProperties,
RecordSet,
Team,
ViewTypeMask,
)

Expand Down Expand Up @@ -147,6 +148,12 @@ def init(self, syn: Synapse, schedule_for_cleanup: Callable[..., None]) -> None:
self.syn = syn
self.schedule_for_cleanup = schedule_for_cleanup

@pytest.fixture(scope="function")
def team(self) -> Team:
team = Team(name=f"test_team_{uuid.uuid4()}").create(synapse_client=self.syn)
self.schedule_for_cleanup(team)
return team

@pytest.fixture(scope="function")
def folder_with_view(self, project_model: Project) -> tuple[Folder, EntityView]:
"""Create a folder with an associated EntityView for file-based testing."""
Expand Down Expand Up @@ -239,7 +246,7 @@ def record_set(self, project_model: Project) -> RecordSet:
raise

def test_store_file_based_curation_task(
self, project_model: Project, folder_with_view: tuple[Folder, EntityView]
self, team, project_model: Project, folder_with_view: tuple[Folder, EntityView]
) -> None:
# GIVEN a project, folder, and entity view
folder, entity_view = folder_with_view
Expand All @@ -257,6 +264,7 @@ def test_store_file_based_curation_task(
project_id=project_model.id,
instructions="Please curate this test data.",
task_properties=task_properties,
assignee_principal_id=str(team.id),
)

# WHEN I store the curation task
Expand All @@ -273,9 +281,10 @@ def test_store_file_based_curation_task(
assert stored_task.etag is not None
assert stored_task.created_on is not None
assert stored_task.created_by is not None
assert stored_task.assignee_principal_id == str(team.id)

def test_store_record_based_curation_task(
self, project_model: Project, record_set: RecordSet
self, project_model: Project, record_set: RecordSet, team: Team
) -> None:
# GIVEN a project and record set
# AND a RecordBasedMetadataTaskProperties
Expand All @@ -290,6 +299,7 @@ def test_store_record_based_curation_task(
project_id=project_model.id,
instructions="Please curate this record-based test data.",
task_properties=task_properties,
assignee_principal_id=str(team.id),
)

# WHEN I store the curation task
Expand All @@ -307,6 +317,7 @@ def test_store_record_based_curation_task(
assert stored_task.etag is not None
assert stored_task.created_on is not None
assert stored_task.created_by is not None
assert stored_task.assignee_principal_id == str(team.id)

def test_store_update_existing_curation_task(
self, project_model: Project, record_set: RecordSet
Expand Down
Loading
Loading