Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
c748132
tdd initial tests
jaymedina Sep 29, 2025
33643ae
initial intro of dataclasses
jaymedina Sep 29, 2025
8a074e6
expose api services for submission object
jaymedina Sep 30, 2025
c97b891
style and update docstring
jaymedina Sep 30, 2025
f79f683
add submission and submissionstatus models
jaymedina Oct 10, 2025
99ebaa1
add submission status retrieval and update methods; remove empty subm…
jaymedina Oct 10, 2025
33c651f
pipe query params directly into restAPI httpx requests
jaymedina Oct 14, 2025
ae05b91
new dataclass object submission_bundle
jaymedina Oct 14, 2025
f51188d
move submission services functions to evaluation_services.py
jaymedina Nov 5, 2025
33f7a6a
renaming imports, to_synapse_request, request body refactor
jaymedina Nov 10, 2025
4a91a2e
patching up store method signature
jaymedina Nov 10, 2025
65eac22
update docs
jaymedina Nov 10, 2025
a5ec33a
new suite of tests
jaymedina Nov 10, 2025
a160585
submissionstatus rework as a mutable object
jaymedina Nov 10, 2025
660259e
bug fix for Statuses: updated to_synapse_request to follow same patte…
jaymedina Nov 11, 2025
55a229c
replace != with is not for full object comparison (not just keys)
jaymedina Nov 11, 2025
3479046
expose the is_private arg for to_submission_status_annotations ONLY F…
jaymedina Nov 11, 2025
896a3c1
fixed submission status/submission annotations
jaymedina Nov 12, 2025
26a02f1
add support for legacy annotations
jaymedina Nov 13, 2025
271181a
remove debug prints
jaymedina Nov 13, 2025
c8c1041
get_all_submission_statuses now returns a list of substat objects
jaymedina Nov 13, 2025
0436931
docstring updates
jaymedina Nov 13, 2025
cb48df2
update submissionbundle docstrings, add more examples
jaymedina Nov 14, 2025
dbe5f76
initial sync test for status. moved evaluation_id retrieval to fill_f…
jaymedina Nov 14, 2025
7c07af9
update submissionBundle submissionstatus with evaluation_id
jaymedina Nov 14, 2025
6a5e97b
patch sync substatus integ tests. style.
jaymedina Nov 14, 2025
9c2c0d1
fix submissionStatus integ tests and has_changed attribute
jaymedina Nov 17, 2025
aee4ccd
new substatus async integ tests. can_cancel can now be modified by an…
jaymedina Nov 20, 2025
4a5fe53
new test class for submission cancel functionality
jaymedina Nov 20, 2025
480d5de
substatus async unit tests
jaymedina Nov 20, 2025
4ca53e0
remove compare=false for some attributes. update sync unit tests
jaymedina Nov 20, 2025
3f25df8
add submissionBundle integration tests
jaymedina Nov 20, 2025
b5839c3
add submissionBundle unit tests
jaymedina Nov 20, 2025
5526b8d
remove unnecessary imports and add style
jaymedina Nov 20, 2025
bdeaaa7
get_evaluation_submissions returns generator object
jaymedina Nov 21, 2025
7add157
get_user_submissions returns generator object
jaymedina Nov 21, 2025
35696eb
submissionBundle methods return generators
jaymedina Nov 21, 2025
b8c0ee5
address final todo: implement docker_tag, and note in docs that versi…
jaymedina Nov 21, 2025
73c5f1a
import -> imports
jaymedina Dec 1, 2025
8db9e67
change back to import. patch uses synapse logger instance.
jaymedina Dec 1, 2025
53d9852
lock mkdocstrings-python to >=2.0.0
jaymedina Dec 2, 2025
1561141
Merge branch 'synpy-1590-submission-model-main' into synpy-1590-submi…
jaymedina Dec 2, 2025
4d082c3
add Return description to create_submission
jaymedina Dec 2, 2025
2272d47
[SYNPY-1714] Fix Issues with Failing Tests (#1287)
SageGJ Dec 3, 2025
134ad0c
no need to build out Annotations object from scratch (remove evaluati…
jaymedina Dec 4, 2025
48468c6
remove Dict, List imports
jaymedina Dec 4, 2025
23a2779
fix broken tests due to removed evaluation_id attr
jaymedina Dec 4, 2025
72a4dd0
style
jaymedina Dec 4, 2025
8dd2db4
import classes directly from typing. remove Dict and List.
jaymedina Dec 4, 2025
b75c566
style
jaymedina Dec 4, 2025
be4278a
add API reference links to _fetch_latest_entity
jaymedina Dec 4, 2025
5a70bd1
type hint should be logging.Logger instance (generic or Synapse client)
jaymedina Dec 4, 2025
a54ade1
explicit import to follow the other imports
jaymedina Dec 4, 2025
890005e
import order
jaymedina Dec 4, 2025
0b0fa0c
minimize indenting by using if not:
jaymedina Dec 4, 2025
96f2dd7
raise ValueError(no etag for entity) sooner
jaymedina Dec 5, 2025
acedf26
remove docker submission async integration test
jaymedina Dec 5, 2025
75bad96
link Jira ticket to TODO item
jaymedina Dec 5, 2025
ea52424
remove old cancel submission test
jaymedina Dec 5, 2025
f08a6ad
remove old cancel submission test (async)
jaymedina Dec 5, 2025
e5189d6
assert the SubmissionStatus object has not changed
jaymedina Dec 5, 2025
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
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ docs =
mkdocs>=1.5.3
mkdocs-material>=9.4.14
mkdocstrings>=0.24.0
mkdocstrings-python>=1.8.0
mkdocstrings-python>=2.0.0
termynal>=0.11.1
mkdocs-open-in-new-tab~=1.0.3
markdown-include~=0.8.1
Expand Down
142 changes: 131 additions & 11 deletions synapseclient/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@

import collections
import datetime
import typing
from logging import Logger
from typing import Any, Callable, Mapping, Optional, Union

from deprecated import deprecated

Expand All @@ -95,8 +96,8 @@ def raise_anno_type_error(anno_type: str):
raise ValueError(f"Unknown type in annotations response: {anno_type}")


ANNO_TYPE_TO_FUNC: typing.Dict[
str, typing.Callable[[str], typing.Union[str, int, float, datetime.datetime]]
ANNO_TYPE_TO_FUNC: dict[
str, Callable[[str], Union[str, int, float, datetime.datetime]]
] = collections.defaultdict(
raise_anno_type_error,
{
Expand All @@ -109,7 +110,7 @@ def raise_anno_type_error(anno_type: str):
)


def is_synapse_annotations(annotations: typing.Mapping) -> bool:
def is_synapse_annotations(annotations: Mapping) -> bool:
"""Tests if the given object is a Synapse-style Annotations object.

Arguments:
Expand All @@ -125,7 +126,7 @@ def is_synapse_annotations(annotations: typing.Mapping) -> bool:
return annotations.keys() >= {"id", "etag", "annotations"}


def _annotation_value_list_element_type(annotation_values: typing.List):
def _annotation_value_list_element_type(annotation_values: list):
if not annotation_values:
raise ValueError("annotations value list can not be empty")

Expand Down Expand Up @@ -228,6 +229,127 @@ def to_submission_status_annotations(annotations, is_private=True):
return synapseAnnos


def to_submission_annotations(
id: Union[str, int],
etag: str,
annotations: dict[str, Any],
logger: Optional[Logger] = None,
) -> dict[str, Any]:
"""
Converts a normal dictionary to the format used for submission annotations, which is different from the format
used to annotate entities.

This function creates the proper nested structure that includes id, etag, and annotations in the format
expected by the submissionAnnotations field of a SubmissionStatus request body.

Arguments:
id: The unique ID of the submission being annotated.
etag: The etag of the submission status for optimistic concurrency control.
annotations: A normal Python dictionary comprised of the annotations to be added.
logger: An optional logger instance. If not provided, a default logger will be used.

Returns:
A dictionary in the format expected by submissionAnnotations with nested structure containing
id, etag, and annotations object with type/value format.

Example: Using this function
Converting annotations to submission format

from synapseclient.annotations import to_submission_annotations

# Input annotations
my_annotations = {
"score": 85,
"feedback": "Good work!"
}

# Convert to submission annotations format
submission_annos = to_submission_annotations(
id="9999999",
etag="abc123",
annotations=my_annotations,
is_private=True
)

# Result:
# {
# "id": "9999999",
# "etag": "abc123",
# "annotations": {
# "score": {"type": "INTEGER", "value": [85]},
# "feedback": {"type": "STRING", "value": ["Good work!"]},
# }
# }

Note:
This function is designed specifically for the submissionAnnotations field format,
which is part of the creation of a SubmissionStatus request body:

<https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/annotation/v2/Annotations.html>
"""
# Create the base structure
submission_annos = {"id": str(id), "etag": str(etag), "annotations": {}}

# Convert each annotation to the proper nested format
for key, value in annotations.items():
# Ensure value is a list
if not isinstance(value, list):
value_list = [value]
else:
value_list = value

# Warn about empty annotation values and skip them
if not value_list:
if logger:
logger.warning(
f"Annotation '{key}' has an empty value list and will be skipped"
)
else:
from synapseclient import Synapse

client = Synapse.get_client()
client.logger.warning(
f"Annotation '{key}' has an empty value list and will be skipped"
)
continue

# Determine type based on the first element
first_element = value_list[0]

if isinstance(first_element, str):
submission_annos["annotations"][key] = {
"type": "STRING",
"value": value_list,
}
elif isinstance(first_element, bool):
# Convert booleans to lowercase strings
submission_annos["annotations"][key] = {
"type": "STRING",
"value": [str(v).lower() for v in value_list],
}
elif isinstance(first_element, int):
submission_annos["annotations"][key] = {"type": "LONG", "value": value_list}
elif isinstance(first_element, float):
submission_annos["annotations"][key] = {
"type": "DOUBLE",
"value": value_list,
}
elif is_date(first_element):
# Convert dates to unix timestamps
submission_annos["annotations"][key] = {
"type": "LONG",
"value": [to_unix_epoch_time(v) for v in value_list],
}
else:
# Default to string representation
submission_annos["annotations"][key] = {
"type": "STRING",
"value": [str(v) for v in value_list],
}

return submission_annos


# TODO: this should accept a status object and return its annotations or an empty dict if there are none
def from_submission_status_annotations(annotations) -> dict:
"""
Expand Down Expand Up @@ -347,9 +469,9 @@ class Annotations(dict):

def __init__(
self,
id: typing.Union[str, int, Entity],
id: Union[str, int, Entity],
etag: str,
values: typing.Dict = None,
values: dict = None,
**kwargs,
):
"""
Expand Down Expand Up @@ -404,7 +526,7 @@ def etag(self, value):
self._etag = str(value)


def to_synapse_annotations(annotations: Annotations) -> typing.Dict[str, typing.Any]:
def to_synapse_annotations(annotations: Annotations) -> dict[str, Any]:
"""Transforms a simple flat dictionary to a Synapse-style Annotation object. See
the [Synapse API](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/annotation/v2/Annotations.html)
documentation for more information on Synapse-style Annotation objects.
Expand Down Expand Up @@ -459,9 +581,7 @@ def _convert_to_annotations_list(annotations):
return nested_annos


def from_synapse_annotations(
raw_annotations: typing.Dict[str, typing.Any]
) -> Annotations:
def from_synapse_annotations(raw_annotations: dict[str, Any]) -> Annotations:
"""Transforms a Synapse-style Annotation object to a simple flat dictionary.

Arguments:
Expand Down
27 changes: 27 additions & 0 deletions synapseclient/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,28 @@
update_entity_acl,
)
from .evaluation_services import (
batch_update_submission_statuses,
cancel_submission,
create_or_update_evaluation,
create_submission,
delete_evaluation,
delete_submission,
get_all_evaluations,
get_all_submission_statuses,
get_available_evaluations,
get_evaluation,
get_evaluation_acl,
get_evaluation_permissions,
get_evaluation_submission_bundles,
get_evaluation_submissions,
get_evaluations_by_project,
get_submission,
get_submission_count,
get_submission_status,
get_user_submission_bundles,
get_user_submissions,
update_evaluation_acl,
update_submission_status,
)
from .file_services import (
AddPartResponse,
Expand Down Expand Up @@ -282,4 +295,18 @@
"get_evaluation_acl",
"update_evaluation_acl",
"get_evaluation_permissions",
# submission-related evaluation services
"create_submission",
"get_submission",
"get_evaluation_submissions",
"get_user_submissions",
"get_submission_count",
"delete_submission",
"cancel_submission",
"get_submission_status",
"update_submission_status",
"get_all_submission_statuses",
"batch_update_submission_statuses",
"get_evaluation_submission_bundles",
"get_user_submission_bundles",
]
Loading
Loading