Skip to content
Draft
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
19 changes: 16 additions & 3 deletions synapseclient/extensions/curator/file_based_metadata_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from synapseclient.core.exceptions import SynapseHTTPError # type: ignore
from synapseclient.extensions.curator.utils import project_id_from_entity_id
from synapseclient.models import ( # type: ignore
AuthorizationMode,
Column,
ColumnType,
EntityView,
Expand Down Expand Up @@ -325,6 +326,8 @@ def create_file_based_metadata_task(
enable_derived_annotations: bool = False,
assignee_principal_id: Optional[Union[str, int]] = None,
view_type_mask: Union[int, ViewTypeMask] = ViewTypeMask.FILE,
suggested_authorization_mode: Optional[Union[AuthorizationMode, str]] = None,
collaborator_principal_ids: Optional[list[str]] = None,
*,
synapse_client: Optional[Synapse] = None,
) -> Tuple[str, str]:
Expand All @@ -338,7 +341,7 @@ def create_file_based_metadata_task(
```python
import synapseclient
from synapseclient.extensions.curator import create_file_based_metadata_task
from synapseclient.models import ViewTypeMask
from synapseclient.models import AuthorizationMode, ViewTypeMask

syn = synapseclient.Synapse()
syn.login()
Expand All @@ -351,8 +354,9 @@ def create_file_based_metadata_task(
attach_wiki=False,
entity_view_name="Biospecimen Metadata View",
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)
view_type_mask=ViewTypeMask.FILE | ViewTypeMask.DOCKER, # Optional: include additional entity types in the view
assignee_principal_id=123456, # Optional: Assign to a user or team (can be str or int)
view_type_mask=ViewTypeMask.FILE | ViewTypeMask.DOCKER, # Optional: include additional entity types in the view
suggested_authorization_mode=AuthorizationMode.SOURCE_BENEFACTOR,
)
```

Expand All @@ -377,6 +381,13 @@ def create_file_based_metadata_task(
ViewTypeMask.FILE. Additional types can be added using bitwise OR
(e.g., ViewTypeMask.FILE | ViewTypeMask.DOCKER). Accepts either a
ViewTypeMask enum member or its raw integer value.
suggested_authorization_mode: The authorization mode a client should use when
creating a linked grid session for this task. When omitted, clients follow
legacy behavior: find or create a personal, unlinked grid session.
collaborator_principal_ids: The set of principal IDs that should collaborate on
the grid session. Used to set the owner(s) of a linked GridSession when
suggested_authorization_mode is SESSION_OWNER. Reserved for future
multi-owner support; not actively used at this time.
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 @@ -481,6 +492,8 @@ def create_file_based_metadata_task(
task_properties=FileBasedMetadataTaskProperties(
upload_folder_id=folder_id,
file_view_id=entity_view_id,
suggested_authorization_mode=suggested_authorization_mode,
collaborator_principal_ids=collaborator_principal_ids,
),
).store(synapse_client=synapse_client)
except Exception as e:
Expand Down
17 changes: 15 additions & 2 deletions synapseclient/extensions/curator/record_based_metadata_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from synapseclient.core.utils import test_import_pandas
from synapseclient.extensions.curator.utils import project_id_from_entity_id
from synapseclient.models import (
AuthorizationMode,
CurationTask,
Grid,
JSONSchema,
Expand Down Expand Up @@ -111,6 +112,8 @@ def create_record_based_metadata_task(
bind_schema_to_record_set: bool = True,
enable_derived_annotations: bool = False,
assignee_principal_id: Optional[Union[str, int]] = None,
suggested_authorization_mode: Optional[Union[AuthorizationMode, str]] = None,
collaborator_principal_ids: Optional[list[str]] = None,
*,
synapse_client: Optional[Synapse] = None,
project_id: Optional[str] = None, # Deprecated, will be removed in v5.0.0
Expand All @@ -136,13 +139,13 @@ def create_record_based_metadata_task(
Example: Creating a record-based metadata curation task with a schema URI
In this example, we create a RecordSet and CurationTask for biospecimen metadata
curation using a schema URI. By default this will also bind the schema to the
RecordSet, however the `bind_schema_to_record_set` parameter can be set to
RecordSet, however the bind_schema_to_record_set parameter can be set to
False to skip that step.


```python
import synapseclient
from synapseclient.extensions.curator import create_record_based_metadata_task
from synapseclient.models import AuthorizationMode

syn = synapseclient.Synapse()
syn.login()
Expand All @@ -157,6 +160,7 @@ def create_record_based_metadata_task(
instructions="Please curate this metadata according to the schema requirements",
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)
suggested_authorization_mode=AuthorizationMode.SOURCE_BENEFACTOR,
create_grid=False, # Opt out of deprecated Grid creation
)
```
Expand All @@ -182,6 +186,13 @@ def create_record_based_metadata_task(
(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.
suggested_authorization_mode: The authorization mode a client should use when
creating a linked grid session for this task. When omitted, clients follow
legacy behavior: find or create a personal, unlinked grid session.
collaborator_principal_ids: The set of principal IDs that should collaborate on
the grid session. Used to set the owner(s) of a linked GridSession when
suggested_authorization_mode is SESSION_OWNER. Reserved for future
multi-owner support; not actively used at this time.
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 @@ -286,6 +297,8 @@ def create_record_based_metadata_task(
),
task_properties=RecordBasedMetadataTaskProperties(
record_set_id=record_set_id,
suggested_authorization_mode=suggested_authorization_mode,
collaborator_principal_ids=collaborator_principal_ids,
),
).store(synapse_client=synapse_client)
synapse_client.logger.info(
Expand Down
2 changes: 2 additions & 0 deletions synapseclient/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
)
from synapseclient.models.annotations import Annotations
from synapseclient.models.curation import (
AuthorizationMode,
CurationTask,
FileBasedMetadataTaskProperties,
Grid,
Expand Down Expand Up @@ -94,6 +95,7 @@
"Team",
"TeamMember",
"TeamMembershipStatus",
"AuthorizationMode",
"CurationTask",
"FileBasedMetadataTaskProperties",
"RecordBasedMetadataTaskProperties",
Expand Down
106 changes: 95 additions & 11 deletions synapseclient/models/curation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@
from dataclasses import dataclass, field, replace
from datetime import datetime, timezone
from enum import Enum
from typing import Any, AsyncGenerator, Dict, Generator, Optional, Protocol, Union
from typing import (
Any,
AsyncGenerator,
ClassVar,
Dict,
Generator,
Optional,
Protocol,
Union,
)

from opentelemetry import trace

Expand Down Expand Up @@ -52,6 +61,7 @@
merge_dataclass_entities,
)
from synapseclient.models.mixins.asynchronous_job import AsynchronousCommunicator
from synapseclient.models.mixins.enum_coercion import EnumCoercionMixin
from synapseclient.models.recordset import ValidationSummary
from synapseclient.models.table_components import Column, CsvTableDescriptor, Query

Expand All @@ -76,8 +86,23 @@ class TaskState(str, Enum):
"""The task has been canceled and is no longer needed."""


class AuthorizationMode(str, Enum):
"""
The authorization mode a client should use when creating a linked grid session
for a CurationTask.

See <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/AuthorizationMode.html>.
"""

SESSION_OWNER = "SESSION_OWNER"
"""The grid session is owned by one or more explicit principals (collaborator_principal_ids)."""

SOURCE_BENEFACTOR = "SOURCE_BENEFACTOR"
"""The grid session inherits permissions from the benefactor of the source entity."""


@dataclass
class FileBasedMetadataTaskProperties:
class FileBasedMetadataTaskProperties(EnumCoercionMixin):
"""
A CurationTaskProperties for file-based data, describing where data is uploaded
and a view which contains the annotations.
Expand All @@ -89,12 +114,28 @@ class FileBasedMetadataTaskProperties:
file_view_id: The synId of the FileView that shows all data of this type
"""

_ENUM_FIELDS: ClassVar[dict[str, type]] = {
"suggested_authorization_mode": AuthorizationMode
}

upload_folder_id: Optional[str] = None
"""The synId of the folder where data files of this type are to be uploaded"""

file_view_id: Optional[str] = None
"""The synId of the FileView that shows all data of this type"""

suggested_authorization_mode: Optional[Union[AuthorizationMode, str]] = None
"""The authorization mode a client should use when creating a linked grid session for
this task. When omitted, clients follow legacy behavior: find or create a personal,
unlinked grid session. When this field changes, the server automatically clears
activeSessionId from the task status. Accepts either an AuthorizationMode enum
value or its string equivalent (e.g., "SOURCE_BENEFACTOR")."""

collaborator_principal_ids: Optional[list[str]] = None
"""The set of principal IDs that should collaborate on the grid session. Used to set
the owner(s) of a linked GridSession when suggested_authorization_mode is SESSION_OWNER.
Reserved for future multi-owner support; not actively used at this time."""

def fill_from_dict(
self, synapse_response: Union[Dict[str, Any], Any]
) -> "FileBasedMetadataTaskProperties":
Expand All @@ -109,6 +150,12 @@ def fill_from_dict(
"""
self.upload_folder_id = synapse_response.get("uploadFolderId", None)
self.file_view_id = synapse_response.get("fileViewId", None)
self.suggested_authorization_mode = synapse_response.get(
"suggestedAuthorizationMode", None
)
self.collaborator_principal_ids = synapse_response.get(
"collaboratorPrincipalIds", None
)
return self

def to_synapse_request(self) -> Dict[str, Any]:
Expand All @@ -118,16 +165,23 @@ def to_synapse_request(self) -> Dict[str, Any]:
Returns:
A dictionary representation of this object for API requests.
"""
request_dict = {"concreteType": FILE_BASED_METADATA_TASK_PROPERTIES}
if self.upload_folder_id is not None:
request_dict["uploadFolderId"] = self.upload_folder_id
if self.file_view_id is not None:
request_dict["fileViewId"] = self.file_view_id
request_dict = {
"concreteType": FILE_BASED_METADATA_TASK_PROPERTIES,
"uploadFolderId": self.upload_folder_id,
"fileViewId": self.file_view_id,
"suggestedAuthorizationMode": (
self.suggested_authorization_mode.value
if self.suggested_authorization_mode is not None
else None
),
"collaboratorPrincipalIds": self.collaborator_principal_ids,
}
delete_none_keys(request_dict)
return request_dict


@dataclass
class RecordBasedMetadataTaskProperties:
class RecordBasedMetadataTaskProperties(EnumCoercionMixin):
"""
A CurationTaskProperties for record-based metadata.

Expand All @@ -137,9 +191,25 @@ class RecordBasedMetadataTaskProperties:
record_set_id: The synId of the RecordSet that will contain all record-based metadata
"""

_ENUM_FIELDS: ClassVar[Dict[str, type]] = {
"suggested_authorization_mode": AuthorizationMode
}

record_set_id: Optional[str] = None
"""The synId of the RecordSet that will contain all record-based metadata"""

suggested_authorization_mode: Optional[Union[AuthorizationMode, str]] = None
"""The authorization mode a client should use when creating a linked grid session for
this task. When omitted, clients follow legacy behavior: find or create a personal,
unlinked grid session. When this field changes, the server automatically clears
activeSessionId from the task status. Accepts either an AuthorizationMode enum
value or its string equivalent (e.g., "SOURCE_BENEFACTOR")."""

collaborator_principal_ids: Optional[list[str]] = None
"""The set of principal IDs that should collaborate on the grid session. Used to set
the owner(s) of a linked GridSession when suggested_authorization_mode is SESSION_OWNER.
Reserved for future multi-owner support; not actively used at this time."""

def fill_from_dict(
self, synapse_response: Union[Dict[str, Any], Any]
) -> "RecordBasedMetadataTaskProperties":
Expand All @@ -153,6 +223,12 @@ def fill_from_dict(
The RecordBasedMetadataTaskProperties object.
"""
self.record_set_id = synapse_response.get("recordSetId", None)
self.suggested_authorization_mode = synapse_response.get(
"suggestedAuthorizationMode", None
)
self.collaborator_principal_ids = synapse_response.get(
"collaboratorPrincipalIds", None
)
return self

def to_synapse_request(self) -> Dict[str, Any]:
Expand All @@ -162,9 +238,17 @@ def to_synapse_request(self) -> Dict[str, Any]:
Returns:
A dictionary representation of this object for API requests.
"""
request_dict = {"concreteType": RECORD_BASED_METADATA_TASK_PROPERTIES}
if self.record_set_id is not None:
request_dict["recordSetId"] = self.record_set_id
request_dict = {
"concreteType": RECORD_BASED_METADATA_TASK_PROPERTIES,
"recordSetId": self.record_set_id,
"suggestedAuthorizationMode": (
self.suggested_authorization_mode.value
if self.suggested_authorization_mode is not None
else None
),
"collaboratorPrincipalIds": self.collaborator_principal_ids,
}
delete_none_keys(request_dict)
return request_dict


Expand Down
Loading