Skip to content

Curator

API reference

synapseclient.models.CurationTask dataclass

Bases: CurationTaskSynchronousProtocol

The CurationTask provides instructions for a Data Contributor on how data or metadata of a specific type should be both added to a project and curated.

Represents a Synapse CurationTask.

ATTRIBUTE DESCRIPTION
task_id

The unique identifier issued to this task when it was created

TYPE: Optional[int]

data_type

Will match the data type that a contributor plans to contribute

TYPE: Optional[str]

project_id

The synId of the project

TYPE: Optional[str]

instructions

Instructions to the data contributor

TYPE: Optional[str]

task_properties

The properties of a CurationTask. This can be either FileBasedMetadataTaskProperties or RecordBasedMetadataTaskProperties.

TYPE: Optional[Union[FileBasedMetadataTaskProperties, RecordBasedMetadataTaskProperties]]

etag

Synapse employs an Optimistic Concurrency Control (OCC) scheme to handle concurrent updates. Since the E-Tag changes every time an entity is updated it is used to detect when a client's current representation of an entity is out-of-date.

TYPE: Optional[str]

created_on

(Read Only) The date this task was created

TYPE: Optional[str]

modified_on

(Read Only) The date this task was last modified

TYPE: Optional[str]

created_by

(Read Only) The ID of the user that created this task

TYPE: Optional[str]

modified_by

(Read Only) The ID of the user that last modified this task

TYPE: Optional[str]

Complete curation task workflow

 

from synapseclient import Synapse
from synapseclient.models import CurationTask, FileBasedMetadataTaskProperties

syn = Synapse()
syn.login()

# Create a new file-based curation task
file_properties = FileBasedMetadataTaskProperties(
    upload_folder_id="syn1234567",
    file_view_id="syn2345678"
)

task = CurationTask(
    project_id="syn9876543",
    data_type="genomics_data",
    instructions="Upload your genomics files and complete metadata",
    task_properties=file_properties
)
task = task.store()
print(f"Created task: {task.task_id}")

# Later, retrieve and update the task
existing_task = CurationTask(task_id=task.task_id).get()
existing_task.instructions = "Updated instructions with new requirements"
existing_task.store()

# List all tasks in the project
for project_task in CurationTask.list(project_id="syn9876543"):
    print(f"Task: {project_task.data_type} - {project_task.task_id}")
Source code in synapseclient/models/curation.py
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
@dataclass
@async_to_sync
class CurationTask(CurationTaskSynchronousProtocol):
    """
    The CurationTask provides instructions for a Data Contributor on how data or metadata
    of a specific type should be both added to a project and curated.

    Represents a [Synapse CurationTask](https://rest-docs.synapse.org/org/sagebionetworks/repo/model/curation/CurationTask.html).

    Attributes:
        task_id: The unique identifier issued to this task when it was created
        data_type: Will match the data type that a contributor plans to contribute
        project_id: The synId of the project
        instructions: Instructions to the data contributor
        task_properties: The properties of a CurationTask. This can be either
            FileBasedMetadataTaskProperties or RecordBasedMetadataTaskProperties.
        etag: Synapse employs an Optimistic Concurrency Control (OCC) scheme to handle
            concurrent updates. Since the E-Tag changes every time an entity is updated
            it is used to detect when a client's current representation of an entity is
            out-of-date.
        created_on: (Read Only) The date this task was created
        modified_on: (Read Only) The date this task was last modified
        created_by: (Read Only) The ID of the user that created this task
        modified_by: (Read Only) The ID of the user that last modified this task

    Example: Complete curation task workflow
         

        ```python
        from synapseclient import Synapse
        from synapseclient.models import CurationTask, FileBasedMetadataTaskProperties

        syn = Synapse()
        syn.login()

        # Create a new file-based curation task
        file_properties = FileBasedMetadataTaskProperties(
            upload_folder_id="syn1234567",
            file_view_id="syn2345678"
        )

        task = CurationTask(
            project_id="syn9876543",
            data_type="genomics_data",
            instructions="Upload your genomics files and complete metadata",
            task_properties=file_properties
        )
        task = task.store()
        print(f"Created task: {task.task_id}")

        # Later, retrieve and update the task
        existing_task = CurationTask(task_id=task.task_id).get()
        existing_task.instructions = "Updated instructions with new requirements"
        existing_task.store()

        # List all tasks in the project
        for project_task in CurationTask.list(project_id="syn9876543"):
            print(f"Task: {project_task.data_type} - {project_task.task_id}")
        ```
    """

    task_id: Optional[int] = None
    """The unique identifier issued to this task when it was created"""

    data_type: Optional[str] = None
    """Will match the data type that a contributor plans to contribute. The dataType must be unique within a project"""

    project_id: Optional[str] = None
    """The synId of the project"""

    instructions: Optional[str] = None
    """Instructions to the data contributor"""

    task_properties: Optional[
        Union[FileBasedMetadataTaskProperties, RecordBasedMetadataTaskProperties]
    ] = None
    """The properties of a CurationTask"""

    etag: Optional[str] = None
    """Synapse employs an Optimistic Concurrency Control (OCC) scheme to handle concurrent updates. Since the E-Tag changes every time an entity is updated it is used to detect when a client's current representation of an entity is out-of-date"""

    created_on: Optional[str] = None
    """(Read Only) The date this task was created"""

    modified_on: Optional[str] = None
    """(Read Only) The date this task was last modified"""

    created_by: Optional[str] = None
    """(Read Only) The ID of the user that created this task"""

    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
    )
    """The last persistent instance of this object. This is used to determine if the
    object has been changed and needs to be updated in Synapse."""

    @property
    def has_changed(self) -> bool:
        """Determines if the object has been changed and needs to be updated in Synapse."""
        return (
            not self._last_persistent_instance or self._last_persistent_instance != self
        )

    def _set_last_persistent_instance(self) -> None:
        """Stash the last time this object interacted with Synapse. This is used to
        determine if the object has been changed and needs to be updated in Synapse."""
        del self._last_persistent_instance
        self._last_persistent_instance = replace(self)
        self._last_persistent_instance.task_properties = (
            deepcopy(self.task_properties) if self.task_properties else None
        )

    def fill_from_dict(
        self, synapse_response: Union[Dict[str, Any], Any]
    ) -> "CurationTask":
        """
        Converts a response from the REST API into this dataclass.

        Arguments:
            synapse_response: The response from the REST API.

        Returns:
            The CurationTask object.
        """
        self.task_id = (
            int(synapse_response.get("taskId", None))
            if synapse_response.get("taskId", None)
            else None
        )
        self.data_type = synapse_response.get("dataType", None)
        self.project_id = synapse_response.get("projectId", None)
        self.instructions = synapse_response.get("instructions", None)
        self.etag = synapse_response.get("etag", None)
        self.created_on = synapse_response.get("createdOn", None)
        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 is None:
            raise ValueError(
                "taskProperties was not found in the Synapse response for this CurationTask. "
                "This means it is likely an older CurationTask from before taskProperties was added. "
                "It is recommended that this task be deleted: task.delete(delete_source=False) "
                "and then recreate the task with the correct taskProperties."
            )
        self.task_properties = _create_task_properties_from_dict(task_properties_dict)

        return self

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        request_dict = {}
        request_dict["taskId"] = self.task_id
        request_dict["dataType"] = self.data_type
        request_dict["projectId"] = self.project_id
        request_dict["instructions"] = self.instructions
        request_dict["etag"] = self.etag
        request_dict["createdOn"] = self.created_on
        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()

        delete_none_keys(request_dict)
        return request_dict

    async def get_async(
        self, *, synapse_client: Optional[Synapse] = None
    ) -> "CurationTask":
        """
        Gets a CurationTask from Synapse by ID.

        Arguments:
            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.

        Returns:
            CurationTask: The CurationTask object.

        Raises:
            ValueError: If the CurationTask object does not have a task_id.
            ValueError: If the Synapse response does not contain taskProperties.

        Example: Get a curation task asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask

            syn = Synapse()
            syn.login()

            async def main():
                task = await CurationTask(task_id=123).get_async()
                print(f"Data type: {task.data_type}")
                print(f"Instructions: {task.instructions}")

            asyncio.run(main())
            ```
        """
        if not self.task_id:
            raise ValueError("task_id is required to get a CurationTask")

        trace.get_current_span().set_attributes(
            {
                "synapse.task_id": str(self.task_id),
            }
        )

        task_result = await get_curation_task(
            task_id=self.task_id, synapse_client=synapse_client
        )
        self.fill_from_dict(synapse_response=task_result)
        self._set_last_persistent_instance()
        return self

    @otel_trace_method(
        method_to_trace_name=lambda self, **kwargs: (
            f"CurationTask_GetStatus: ID: {self.task_id}"
        )
    )
    async def get_status_async(
        self, *, synapse_client: Synapse | None = None
    ) -> "CurationTaskStatus":
        """
        Gets the status of this CurationTask from Synapse.

        Arguments:
            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.

        Returns:
            The CurationTaskStatus object.

        Raises:
            ValueError: If the CurationTask object does not have a task_id.

        Example: Get the status of a curation task asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask

            syn = Synapse()
            syn.login()

            async def main():
                status = await CurationTask(task_id=123).get_status_async()
                print(status.state)

            asyncio.run(main())
            ```
        """
        if not self.task_id:
            raise ValueError("task_id is required to get a CurationTask status")

        status_result = await get_curation_task_status(
            task_id=self.task_id, synapse_client=synapse_client
        )
        return CurationTaskStatus().fill_from_dict(status_result)

    async def delete_async(
        self,
        delete_source: bool = False,
        *,
        synapse_client: Optional[Synapse] = None,
    ) -> None:
        """
        Deletes a CurationTask from Synapse.

        Arguments:
            delete_source: If True, the associated source data (EntityView or RecordSet) will also be deleted
                if the task is a FileBasedMetadataTask or RecordBasedMetadataTask respectively. Defaults to False.
            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.

        Raises:
            ValueError: If the CurationTask object does not have a task_id.
            ValueError: If delete_source is True but the task properties are not properly set
              to identify the source to delete.

        Example: Delete a curation task asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask

            syn = Synapse()
            syn.login()

            async def main():
                task = CurationTask(task_id=123)
                await task.delete_async()
                print("Task deleted successfully")

            asyncio.run(main())
            ```

        Example: Delete a curation task and its associated data source asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask

            syn = Synapse()
            syn.login()

            async def main():
                task = CurationTask(task_id=123)
                await task.delete_async(delete_source=True)
                print("Task and record set deleted successfully")

            asyncio.run(main())
            ```
        """
        if not self.task_id:
            raise ValueError("task_id is required to delete a CurationTask")

        trace.get_current_span().set_attributes(
            {
                "synapse.task_id": str(self.task_id),
            }
        )

        if delete_source:
            if not self.task_properties:
                await self.get_async(synapse_client=synapse_client)

            if isinstance(self.task_properties, FileBasedMetadataTaskProperties):
                if not self.task_properties.file_view_id:
                    raise ValueError(
                        "Cannot delete Fileview: "
                        "'file_view_id' attribute is missing."
                    )
                from synapseclient.models import EntityView

                await EntityView(id=self.task_properties.file_view_id).delete_async(
                    synapse_client=synapse_client
                )

            elif isinstance(self.task_properties, RecordBasedMetadataTaskProperties):
                if not self.task_properties.record_set_id:
                    raise ValueError(
                        "Cannot delete RecordSet: "
                        "'record_set_id' attribute is missing."
                    )
                from synapseclient.models import RecordSet

                await RecordSet(id=self.task_properties.record_set_id).delete_async(
                    synapse_client=synapse_client
                )

            else:
                raise ValueError(
                    "'task_property' attribute is None. "
                    "Deletion only supports FileBasedMetadataTaskProperties or "
                    "RecordBasedMetadataTaskProperties."
                )

        await delete_curation_task(task_id=self.task_id, synapse_client=synapse_client)

    async def store_async(
        self, *, synapse_client: Optional[Synapse] = None
    ) -> "CurationTask":
        """
        Creates a new CurationTask or updates an existing one on Synapse.

        This method implements non-destructive updates. If a CurationTask with the same
        project_id and data_type exists and this instance hasn't been retrieved from
        Synapse before, it will merge the existing task data with the current instance
        before updating.

        Arguments:
            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.

        Returns:
            CurationTask: The CurationTask object.

        Example: Create a new curation task asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask, FileBasedMetadataTaskProperties

            syn = Synapse()
            syn.login()

            async def main():
                # Create file-based task properties
                file_properties = FileBasedMetadataTaskProperties(
                    upload_folder_id="syn1234567",
                    file_view_id="syn2345678"
                )

                # Create and store the curation task
                task = CurationTask(
                    project_id="syn9876543",
                    data_type="genomics_data",
                    instructions="Upload your genomics files to the specified folder",
                    task_properties=file_properties
                )
                task = await task.store_async()
                print(f"Created task with ID: {task.task_id}")

            asyncio.run(main())
            ```
        """
        if not self.project_id:
            raise ValueError("project_id is required")
        if not self.data_type:
            raise ValueError("data_type is required")

        trace.get_current_span().set_attributes(
            {
                "synapse.data_type": self.data_type or "",
                "synapse.project_id": self.project_id or "",
                "synapse.task_id": str(self.task_id) if self.task_id else "",
            }
        )

        if (
            not self._last_persistent_instance
            and not self.task_id
            and (
                existing_task_id := await _get_existing_curation_task_id(
                    project_id=self.project_id,
                    data_type=self.data_type,
                    synapse_client=synapse_client,
                )
            )
            and (
                existing_task := await CurationTask(task_id=existing_task_id).get_async(
                    synapse_client=synapse_client
                )
            )
        ):
            merge_dataclass_entities(source=existing_task, destination=self)

        if self.task_id:
            task_result = await update_curation_task(
                task_id=self.task_id,
                curation_task=self.to_synapse_request(),
                synapse_client=synapse_client,
            )
            self.fill_from_dict(synapse_response=task_result)
            self._set_last_persistent_instance()
            return self
        else:
            if not self.project_id:
                raise ValueError("project_id is required to create a CurationTask")
            if not self.data_type:
                raise ValueError("data_type is required to create a CurationTask")
            if not self.instructions:
                raise ValueError("instructions is required to create a CurationTask")
            if not self.task_properties:
                raise ValueError("task_properties is required to create a CurationTask")

            task_result = await create_curation_task(
                curation_task=self.to_synapse_request(),
                synapse_client=synapse_client,
            )
            self.fill_from_dict(synapse_response=task_result)
            self._set_last_persistent_instance()
            return self

    @otel_trace_method(
        method_to_trace_name=lambda self, **kwargs: (
            f"CurationTask_UpdateStatus: ID: {self.task_id}"
        )
    )
    async def update_status_async(
        self,
        curation_task_status: "CurationTaskStatus",
        *,
        synapse_client: Synapse | None = None,
    ) -> "CurationTaskStatus":
        """
        Updates the status of this CurationTask on Synapse.

        Arguments:
            curation_task_status: The complete CurationTaskStatus object to update.
            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.

        Returns:
            The updated CurationTaskStatus object.

        Raises:
            ValueError: If the CurationTask object does not have a task_id.

        Example: Update the status of a curation task asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import (
                CurationTask,
                TaskState,
            )

            syn = Synapse()
            syn.login()

            async def main():
                task = CurationTask(task_id=123)
                current = await task.get_status_async()
                current.state = TaskState.COMPLETED
                updated = await task.update_status_async(curation_task_status=current)
                print(updated.state)

            asyncio.run(main())
            ```
        """
        if not self.task_id:
            raise ValueError("task_id is required to update a CurationTask status")

        status_result = await update_curation_task_status(
            task_id=self.task_id,
            curation_task_status=curation_task_status.to_synapse_request(),
            synapse_client=synapse_client,
        )
        return CurationTaskStatus().fill_from_dict(status_result)

    @otel_trace_method(
        method_to_trace_name=lambda self, **kwargs: (
            f"CurationTask_SetActiveGridSession: ID: {self.task_id}"
        )
    )
    async def set_active_grid_session_async(
        self,
        active_session_id: str,
        *,
        synapse_client: Synapse | None = None,
    ) -> "CurationTaskStatus":
        """
        Set the active grid session on this CurationTask's status by replacing
        execution_details with a GridExecutionDetails carrying the given session id.

        Arguments:
            active_session_id: The unique identifier of the active grid session to link.
            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.

        Returns:
            The updated CurationTaskStatus object.

        Raises:
            ValueError: If the CurationTask object does not have a task_id.

        Example: Link a grid session to a curation task asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask, Grid

            syn = Synapse()
            syn.login()

            async def main():
                grid = await Grid(record_set_id="syn1234567").create_async()
                await CurationTask(task_id=123).set_active_grid_session_async(
                    active_session_id=grid.session_id
                )

            asyncio.run(main())
            ```
        """
        status = await self.get_status_async(synapse_client=synapse_client)
        status.execution_details = GridExecutionDetails(
            active_session_id=active_session_id
        )
        return await self.update_status_async(
            curation_task_status=status, synapse_client=synapse_client
        )

    @otel_trace_method(
        method_to_trace_name=lambda self, **kwargs: (
            f"CurationTask_SetTaskState: ID: {self.task_id}"
        )
    )
    async def set_task_state_async(
        self,
        state: "TaskState | str",
        *,
        synapse_client: Synapse | None = None,
    ) -> "CurationTaskStatus":
        """
        Set the state on this CurationTask's status.

        Does not modify execution_details. Fetches the current CurationTaskStatus
        first so the update carries a fresh etag.

        Arguments:
            state: The state to set on this task's status. Accepts a
                TaskState or a string exactly matching one of its members
                (e.g. NOT_STARTED, IN_PROGRESS, COMPLETED, CANCELED).
            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.

        Returns:
            The updated CurationTaskStatus object.

        Raises:
            ValueError: If the CurationTask object does not have a task_id, or
                if state is a string that does not match a TaskState member.

        Example: Mark a curation task as completed asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask, TaskState

            syn = Synapse()
            syn.login()

            async def main():
                await CurationTask(task_id=123).set_task_state_async(
                    state=TaskState.COMPLETED
                )

            asyncio.run(main())
            ```

        Example: Mark a curation task as completed using a string asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask

            syn = Synapse()
            syn.login()

            async def main():
                await CurationTask(task_id=123).set_task_state_async(
                    state="COMPLETED"
                )

            asyncio.run(main())
            ```
        """
        try:
            coerced_state = TaskState(state)
        except ValueError as exc:
            raise ValueError(
                f"{state!r} is not a valid TaskState. "
                f"Expected one of: {[s.value for s in TaskState]}."
            ) from exc

        status = await self.get_status_async(synapse_client=synapse_client)
        status.state = coerced_state
        return await self.update_status_async(
            curation_task_status=status, synapse_client=synapse_client
        )

    @otel_trace_method(
        method_to_trace_name=lambda self, **kwargs: (
            f"CurationTask_CreateGridSession: ID: {self.task_id}"
        )
    )
    async def create_grid_session_async(
        self,
        *,
        owner_principal_id: int | None = None,
        timeout: int = 120,
        synapse_client: Synapse | None = None,
    ) -> "Grid":
        """
        Create a new Grid session for this CurationTask and set it as the active session.

        Picks the Grid seed from this task's task_properties:

        - RecordBasedMetadataTaskProperties uses record_set_id
        - FileBasedMetadataTaskProperties uses an initial_query that selects from
          the file_view_id

        Always creates a new Grid session. To attach an existing session to a task,
        use set_active_grid_session_async instead.

        The new session is created with the task's suggested_authorization_mode
        (from task_properties), which the server uses to determine access:

        - SESSION_OWNER: access is limited to the session owner (owner_principal_id,
          or the caller when not provided) and their team.
        - SOURCE_BENEFACTOR: access is inherited from the benefactor of the source
          entity (anyone with EDIT rights).
        - Unset (legacy): the caller becomes the owner.

        After the Grid is created, updates the CurationTaskStatus to point its
        active_session_id at the new session. If that update fails for any reason,
        the newly created Grid is deleted on a best-effort basis and the original
        exception is re-raised.

        Arguments:
            owner_principal_id: The principal ID (user or team) that will own the
                created grid session. When not provided, the principal ID of the
                caller is used.
            timeout: Seconds to wait for the grid creation job. Defaults to 120.
            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.

        Returns:
            The newly created Grid.

        Raises:
            ValueError: If task_id is unset or task_properties is of an unsupported type.
            SynapseHTTPError: If the RecordSet or EntityView does not exist, or if the
                status update fails. The orphan Grid is deleted on a best-effort basis
                before the error is re-raised.

        Example: Create a grid session for a curation task asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask

            syn = Synapse()
            syn.login()

            async def main():
                grid = await CurationTask(task_id=123).create_grid_session_async()
                print(grid.session_id)

            asyncio.run(main())
            ```
        """
        if not self.task_id:
            raise ValueError(
                "task_id is required to create a CurationTask grid session"
            )

        if not self.task_properties:
            await self.get_async(synapse_client=synapse_client)

        if isinstance(self.task_properties, RecordBasedMetadataTaskProperties):
            if not self.task_properties.record_set_id:
                raise ValueError(
                    "Cannot create grid session: "
                    "task_properties.record_set_id is missing."
                )
            from synapseclient.models import RecordSet

            # raises SynapseHTTPError if RecordSet does not exist
            await RecordSet(id=self.task_properties.record_set_id).get_async(
                synapse_client=synapse_client
            )
            grid = Grid(
                record_set_id=self.task_properties.record_set_id,
                owner_principal_id=owner_principal_id,
                authorization_mode=self.task_properties.suggested_authorization_mode,
            )
        elif isinstance(self.task_properties, FileBasedMetadataTaskProperties):
            if not self.task_properties.file_view_id:
                raise ValueError(
                    "Cannot create grid session: "
                    "task_properties.file_view_id is missing."
                )
            from synapseclient.models import EntityView

            # raises SynapseHTTPError if EntityView does not exist
            await EntityView(id=self.task_properties.file_view_id).get_async(
                synapse_client=synapse_client
            )
            grid = Grid(
                initial_query=Query(
                    sql=f"SELECT * FROM {self.task_properties.file_view_id}"
                ),
                owner_principal_id=owner_principal_id,
                authorization_mode=self.task_properties.suggested_authorization_mode,
            )
        else:
            raise ValueError(
                "task_properties must be a FileBasedMetadataTaskProperties or "
                "RecordBasedMetadataTaskProperties to create a grid session"
            )

        grid = await grid.create_async(
            timeout=timeout,
            synapse_client=synapse_client,
        )

        # Only one grid session can be set as the active one on a a given CurationTask
        # at a any time, though multiple sessions can exist.
        # If two users run this concurrently, one will lose the race and
        # receive a 412 (precondition failed). In that case — or if recording the
        # active session fails for any other reason — delete the session we just
        # created so it doesn't become an orphan. If the delete also fails, log a
        # warning so the caller knows manual cleanup is needed, then re-raise the
        # original exception in all cases.
        try:
            await self.set_active_grid_session_async(
                active_session_id=grid.session_id, synapse_client=synapse_client
            )
        except Exception:
            try:
                await grid.delete_async(synapse_client=synapse_client)
            except Exception:
                Synapse.get_client(synapse_client=synapse_client).logger.warning(
                    "Failed to delete orphan grid session %s after status "
                    "update failure; manual cleanup may be required.",
                    grid.session_id,
                )
            raise

        return grid

    @skip_async_to_sync
    @classmethod
    async def list_async(
        cls,
        project_id: str,
        *,
        assigned_to_me: Optional[bool] = None,
        assignee_ids: Optional[list[str]] = None,
        state_filter: Optional[list[Union["TaskState", str]]] = None,
        synapse_client: Optional[Synapse] = None,
    ) -> AsyncGenerator["CurationTask", None]:
        """
        Generator that yields CurationTasks for a project as they become available.

        Arguments:
            project_id: The synId of the project.
            assigned_to_me: When True, only return tasks assigned to the current user.
                Cannot be combined with assignee_ids.
                False does not mean "tasks not assigned to me".
                Defaults to None.
            assignee_ids: Optional list of principal IDs (users or teams) to filter
                tasks by assignee. Cannot be combined with assigned_to_me=True.
                Passing an empty list raises a ValueError; pass None to return tasks
                for any assignee. Defaults to None.
            state_filter: Optional list of TaskState values or exact-case strings to
                filter tasks by their current state (e.g., "IN_PROGRESS"). Defaults to
                None (all states returned). Passing an empty list raises a ValueError;
                pass None to return tasks in any state.
            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.

        Yields:
            CurationTask objects as they are retrieved from the API.

        Raises:
            ValueError: If state_filter is an empty list.
            ValueError: If assignee_ids is an empty list.
            ValueError: If assigned_to_me is True and assignee_ids is also provided.
            ValueError: If any value in state_filter is not a TaskState member or
                an exact-case string matching a TaskState value (e.g., "IN_PROGRESS").
            ValueError: If the Synapse response for any task does not contain
                taskProperties.

        Example: List all curation tasks in a project asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask

            syn = Synapse()
            syn.login()

            async def main():
                # List all curation tasks in the project
                async for task in CurationTask.list_async(project_id="syn9876543"):
                    print(f"Task ID: {task.task_id}")
                    print(f"Data Type: {task.data_type}")
                    print(f"Instructions: {task.instructions}")
                    print("---")

            asyncio.run(main())
            ```

        Example: List only curation tasks assigned to the current user asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask

            syn = Synapse()
            syn.login()

            async def main():
                async for task in CurationTask.list_async(
                    project_id="syn9876543", assigned_to_me=True
                ):
                    print(f"Task ID: {task.task_id}")
                    print(f"Data Type: {task.data_type}")
                    print("---")

            asyncio.run(main())
            ```

        Example: List only in-progress curation tasks asynchronously
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask, TaskState

            syn = Synapse()
            syn.login()

            async def main():
                async for task in CurationTask.list_async(
                    project_id="syn9876543",
                    state_filter=[TaskState.IN_PROGRESS],
                ):
                    print(f"Task ID: {task.task_id}")
                    print(f"Data Type: {task.data_type}")
                    print("---")

            asyncio.run(main())
            ```

        Example: List only in-progress curation tasks using a string state filter asynchronously
             

            state_filter also accepts plain strings matching TaskState names exactly.

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask

            syn = Synapse()
            syn.login()

            async def main():
                async for task in CurationTask.list_async(
                    project_id="syn9876543",
                    state_filter=["IN_PROGRESS"],
                ):
                    print(f"Task ID: {task.task_id}")
                    print(f"Data Type: {task.data_type}")
                    print("---")

            asyncio.run(main())
            ```
        """
        if state_filter == []:
            raise ValueError(
                "state_filter must not be empty. Pass None to return tasks in any state."
            )
        if assignee_ids == []:
            raise ValueError(
                "assignee_ids must not be empty. Pass None to return tasks for any assignee."
            )
        if assigned_to_me is True and assignee_ids is not None:
            raise ValueError(
                f"assigned_to_me and assignee_ids are mutually exclusive "
                f"and cannot be used together. Got assignee_ids={assignee_ids!r}."
            )

        if state_filter is not None:
            state_filter = coerce_enum_list(TaskState, state_filter)

        trace.get_current_span().set_attributes(
            {
                "synapse.project_id": project_id,
            }
        )

        async for task_dict in list_curation_tasks(
            project_id=project_id,
            assigned_to_me=assigned_to_me,
            assignee_ids=assignee_ids,
            state_filter=state_filter,
            synapse_client=synapse_client,
        ):
            task = cls().fill_from_dict(synapse_response=task_dict)
            yield task

    @otel_trace_method(
        method_to_trace_name=lambda self, **kwargs: (
            f"CurationTask_SynchronizeActiveGridSession: ID: {self.task_id}"
        )
    )
    async def synchronize_active_grid_session_async(
        self,
        *,
        sync_type: Union["SyncType", str],
        synapse_client: Optional[Synapse] = None,
    ) -> Optional["Grid"]:
        """
        Synchronize this task's active grid session against its source entity.

        If task_properties is not yet populated on this object, it is fetched
        from Synapse first. If the task has no active grid session, a warning
        is logged and None is returned; no new grid session is created.

        `sync_type` is always required, for both task types. FileBasedMetadataTaskProperties
        tasks always perform a SyncType.PULL_PUSH regardless of the value passed in.

        Arguments:
            sync_type: The type of synchronization to perform. Required.

                - SyncType.PULL: Update the grid session with the latest data/schema
                  from the source RecordSet, without writing the grid back to it.
                  Use this to preview an incoming schema or data change in the grid
                  before committing it. Only supported for record-based tasks.
                - SyncType.PULL_PUSH: Update the grid session with the latest data
                  from the source, then write the grid's data back to the source
                  (the source RecordSet for record-based tasks, or the referenced
                  entities for file-based tasks). This commits any in-progress
                  curation in the grid as a new version of the source.

                For record-based tasks, this determines whether the call previews
                (PULL) or commits (PULL_PUSH). For file-based tasks, the value is
                ignored and the call always behaves as SyncType.PULL_PUSH.
            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.

        Returns:
            The synchronized Grid, or None if the task has no active grid session.

        Raises:
            ValueError: If task_id is unset, task_properties is of an unsupported
                type, or sync_type is not provided for a record-based task.

        Example: Synchronize a record-based curation task's grid session
             

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask
            from synapseclient.models.curation import SyncType

            syn = Synapse()
            syn.login()

            async def main():
                grid = await CurationTask(task_id=123).synchronize_active_grid_session_async(
                    sync_type=SyncType.PULL_PUSH
                )
                if grid is not None:
                    print(grid.session_id)

            asyncio.run(main())
            ```

        Example: Synchronize a file-based curation task's grid session
             

            File-based tasks always synchronize with SyncType.PULL_PUSH, so any
            value works here -- but `sync_type` still has to be passed.

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask
            from synapseclient.models.curation import SyncType

            syn = Synapse()
            syn.login()

            async def main():
                grid = await CurationTask(task_id=456).synchronize_active_grid_session_async(
                    sync_type=SyncType.PULL_PUSH
                )
                if grid is not None:
                    print(grid.session_id)

            asyncio.run(main())
            ```
        """
        client = Synapse.get_client(synapse_client=synapse_client)

        if not self.task_properties:
            await self.get_async(synapse_client=synapse_client)

        if isinstance(self.task_properties, FileBasedMetadataTaskProperties):
            if sync_type is not None and sync_type != SyncType.PULL_PUSH:
                client.logger.warning(
                    f"Ignoring sync_type={sync_type} for CurationTask "
                    f"{self.task_id}: FileBasedMetadataTaskProperties tasks always "
                    "use SyncType.PULL_PUSH."
                )
            sync_type = SyncType.PULL_PUSH
        elif isinstance(self.task_properties, RecordBasedMetadataTaskProperties):
            if not sync_type:
                raise ValueError(
                    "sync_type must be provided for RecordBasedMetadataTaskProperties"
                )
        else:
            raise ValueError(
                f"Synchronization only supports FileBasedMetadataTaskProperties or "
                f"RecordBasedMetadataTaskProperties, got {type(self.task_properties).__name__}."
            )

        status = await self.get_status_async(synapse_client=synapse_client)
        if (
            status.execution_details is None
            or status.execution_details.active_session_id is None
        ):
            client.logger.warning(
                f"No active grid session found for task {self.task_id}. Skipping "
                "synchronization."
            )
            return None
        active_grid_session_id = status.execution_details.active_session_id

        client.logger.info(
            f"Synchronizing active grid session {active_grid_session_id} for "
            f"task {self.task_id}"
        )
        grid = Grid(session_id=active_grid_session_id)
        return await grid.synchronize_async(
            synapse_client=synapse_client, sync_type=sync_type
        )

Methods:

get_async async

get_async(*, synapse_client: Optional[Synapse] = None) -> CurationTask

Gets a CurationTask from Synapse by ID.

PARAMETER DESCRIPTION
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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
CurationTask

The CurationTask object.

TYPE: CurationTask

RAISES DESCRIPTION
ValueError

If the CurationTask object does not have a task_id.

ValueError

If the Synapse response does not contain taskProperties.

Get a curation task asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask

syn = Synapse()
syn.login()

async def main():
    task = await CurationTask(task_id=123).get_async()
    print(f"Data type: {task.data_type}")
    print(f"Instructions: {task.instructions}")

asyncio.run(main())
Source code in synapseclient/models/curation.py
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
async def get_async(
    self, *, synapse_client: Optional[Synapse] = None
) -> "CurationTask":
    """
    Gets a CurationTask from Synapse by ID.

    Arguments:
        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.

    Returns:
        CurationTask: The CurationTask object.

    Raises:
        ValueError: If the CurationTask object does not have a task_id.
        ValueError: If the Synapse response does not contain taskProperties.

    Example: Get a curation task asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask

        syn = Synapse()
        syn.login()

        async def main():
            task = await CurationTask(task_id=123).get_async()
            print(f"Data type: {task.data_type}")
            print(f"Instructions: {task.instructions}")

        asyncio.run(main())
        ```
    """
    if not self.task_id:
        raise ValueError("task_id is required to get a CurationTask")

    trace.get_current_span().set_attributes(
        {
            "synapse.task_id": str(self.task_id),
        }
    )

    task_result = await get_curation_task(
        task_id=self.task_id, synapse_client=synapse_client
    )
    self.fill_from_dict(synapse_response=task_result)
    self._set_last_persistent_instance()
    return self

get_status_async async

get_status_async(*, synapse_client: Synapse | None = None) -> CurationTaskStatus

Gets the status of this CurationTask from Synapse.

PARAMETER DESCRIPTION
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.

TYPE: Synapse | None DEFAULT: None

RETURNS DESCRIPTION
CurationTaskStatus

The CurationTaskStatus object.

RAISES DESCRIPTION
ValueError

If the CurationTask object does not have a task_id.

Get the status of a curation task asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask

syn = Synapse()
syn.login()

async def main():
    status = await CurationTask(task_id=123).get_status_async()
    print(status.state)

asyncio.run(main())
Source code in synapseclient/models/curation.py
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
@otel_trace_method(
    method_to_trace_name=lambda self, **kwargs: (
        f"CurationTask_GetStatus: ID: {self.task_id}"
    )
)
async def get_status_async(
    self, *, synapse_client: Synapse | None = None
) -> "CurationTaskStatus":
    """
    Gets the status of this CurationTask from Synapse.

    Arguments:
        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.

    Returns:
        The CurationTaskStatus object.

    Raises:
        ValueError: If the CurationTask object does not have a task_id.

    Example: Get the status of a curation task asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask

        syn = Synapse()
        syn.login()

        async def main():
            status = await CurationTask(task_id=123).get_status_async()
            print(status.state)

        asyncio.run(main())
        ```
    """
    if not self.task_id:
        raise ValueError("task_id is required to get a CurationTask status")

    status_result = await get_curation_task_status(
        task_id=self.task_id, synapse_client=synapse_client
    )
    return CurationTaskStatus().fill_from_dict(status_result)

update_status_async async

update_status_async(curation_task_status: CurationTaskStatus, *, synapse_client: Synapse | None = None) -> CurationTaskStatus

Updates the status of this CurationTask on Synapse.

PARAMETER DESCRIPTION
curation_task_status

The complete CurationTaskStatus object to update.

TYPE: CurationTaskStatus

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.

TYPE: Synapse | None DEFAULT: None

RETURNS DESCRIPTION
CurationTaskStatus

The updated CurationTaskStatus object.

RAISES DESCRIPTION
ValueError

If the CurationTask object does not have a task_id.

Update the status of a curation task asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import (
    CurationTask,
    TaskState,
)

syn = Synapse()
syn.login()

async def main():
    task = CurationTask(task_id=123)
    current = await task.get_status_async()
    current.state = TaskState.COMPLETED
    updated = await task.update_status_async(curation_task_status=current)
    print(updated.state)

asyncio.run(main())
Source code in synapseclient/models/curation.py
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
@otel_trace_method(
    method_to_trace_name=lambda self, **kwargs: (
        f"CurationTask_UpdateStatus: ID: {self.task_id}"
    )
)
async def update_status_async(
    self,
    curation_task_status: "CurationTaskStatus",
    *,
    synapse_client: Synapse | None = None,
) -> "CurationTaskStatus":
    """
    Updates the status of this CurationTask on Synapse.

    Arguments:
        curation_task_status: The complete CurationTaskStatus object to update.
        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.

    Returns:
        The updated CurationTaskStatus object.

    Raises:
        ValueError: If the CurationTask object does not have a task_id.

    Example: Update the status of a curation task asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import (
            CurationTask,
            TaskState,
        )

        syn = Synapse()
        syn.login()

        async def main():
            task = CurationTask(task_id=123)
            current = await task.get_status_async()
            current.state = TaskState.COMPLETED
            updated = await task.update_status_async(curation_task_status=current)
            print(updated.state)

        asyncio.run(main())
        ```
    """
    if not self.task_id:
        raise ValueError("task_id is required to update a CurationTask status")

    status_result = await update_curation_task_status(
        task_id=self.task_id,
        curation_task_status=curation_task_status.to_synapse_request(),
        synapse_client=synapse_client,
    )
    return CurationTaskStatus().fill_from_dict(status_result)

set_active_grid_session_async async

set_active_grid_session_async(active_session_id: str, *, synapse_client: Synapse | None = None) -> CurationTaskStatus

Set the active grid session on this CurationTask's status by replacing execution_details with a GridExecutionDetails carrying the given session id.

PARAMETER DESCRIPTION
active_session_id

The unique identifier of the active grid session to link.

TYPE: str

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.

TYPE: Synapse | None DEFAULT: None

RETURNS DESCRIPTION
CurationTaskStatus

The updated CurationTaskStatus object.

RAISES DESCRIPTION
ValueError

If the CurationTask object does not have a task_id.

Link a grid session to a curation task asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask, Grid

syn = Synapse()
syn.login()

async def main():
    grid = await Grid(record_set_id="syn1234567").create_async()
    await CurationTask(task_id=123).set_active_grid_session_async(
        active_session_id=grid.session_id
    )

asyncio.run(main())
Source code in synapseclient/models/curation.py
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
@otel_trace_method(
    method_to_trace_name=lambda self, **kwargs: (
        f"CurationTask_SetActiveGridSession: ID: {self.task_id}"
    )
)
async def set_active_grid_session_async(
    self,
    active_session_id: str,
    *,
    synapse_client: Synapse | None = None,
) -> "CurationTaskStatus":
    """
    Set the active grid session on this CurationTask's status by replacing
    execution_details with a GridExecutionDetails carrying the given session id.

    Arguments:
        active_session_id: The unique identifier of the active grid session to link.
        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.

    Returns:
        The updated CurationTaskStatus object.

    Raises:
        ValueError: If the CurationTask object does not have a task_id.

    Example: Link a grid session to a curation task asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask, Grid

        syn = Synapse()
        syn.login()

        async def main():
            grid = await Grid(record_set_id="syn1234567").create_async()
            await CurationTask(task_id=123).set_active_grid_session_async(
                active_session_id=grid.session_id
            )

        asyncio.run(main())
        ```
    """
    status = await self.get_status_async(synapse_client=synapse_client)
    status.execution_details = GridExecutionDetails(
        active_session_id=active_session_id
    )
    return await self.update_status_async(
        curation_task_status=status, synapse_client=synapse_client
    )

delete_async async

delete_async(delete_source: bool = False, *, synapse_client: Optional[Synapse] = None) -> None

Deletes a CurationTask from Synapse.

PARAMETER DESCRIPTION
delete_source

If True, the associated source data (EntityView or RecordSet) will also be deleted if the task is a FileBasedMetadataTask or RecordBasedMetadataTask respectively. Defaults to False.

TYPE: bool DEFAULT: False

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.

TYPE: Optional[Synapse] DEFAULT: None

RAISES DESCRIPTION
ValueError

If the CurationTask object does not have a task_id.

ValueError

If delete_source is True but the task properties are not properly set to identify the source to delete.

Delete a curation task asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask

syn = Synapse()
syn.login()

async def main():
    task = CurationTask(task_id=123)
    await task.delete_async()
    print("Task deleted successfully")

asyncio.run(main())
Delete a curation task and its associated data source asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask

syn = Synapse()
syn.login()

async def main():
    task = CurationTask(task_id=123)
    await task.delete_async(delete_source=True)
    print("Task and record set deleted successfully")

asyncio.run(main())
Source code in synapseclient/models/curation.py
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
async def delete_async(
    self,
    delete_source: bool = False,
    *,
    synapse_client: Optional[Synapse] = None,
) -> None:
    """
    Deletes a CurationTask from Synapse.

    Arguments:
        delete_source: If True, the associated source data (EntityView or RecordSet) will also be deleted
            if the task is a FileBasedMetadataTask or RecordBasedMetadataTask respectively. Defaults to False.
        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.

    Raises:
        ValueError: If the CurationTask object does not have a task_id.
        ValueError: If delete_source is True but the task properties are not properly set
          to identify the source to delete.

    Example: Delete a curation task asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask

        syn = Synapse()
        syn.login()

        async def main():
            task = CurationTask(task_id=123)
            await task.delete_async()
            print("Task deleted successfully")

        asyncio.run(main())
        ```

    Example: Delete a curation task and its associated data source asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask

        syn = Synapse()
        syn.login()

        async def main():
            task = CurationTask(task_id=123)
            await task.delete_async(delete_source=True)
            print("Task and record set deleted successfully")

        asyncio.run(main())
        ```
    """
    if not self.task_id:
        raise ValueError("task_id is required to delete a CurationTask")

    trace.get_current_span().set_attributes(
        {
            "synapse.task_id": str(self.task_id),
        }
    )

    if delete_source:
        if not self.task_properties:
            await self.get_async(synapse_client=synapse_client)

        if isinstance(self.task_properties, FileBasedMetadataTaskProperties):
            if not self.task_properties.file_view_id:
                raise ValueError(
                    "Cannot delete Fileview: "
                    "'file_view_id' attribute is missing."
                )
            from synapseclient.models import EntityView

            await EntityView(id=self.task_properties.file_view_id).delete_async(
                synapse_client=synapse_client
            )

        elif isinstance(self.task_properties, RecordBasedMetadataTaskProperties):
            if not self.task_properties.record_set_id:
                raise ValueError(
                    "Cannot delete RecordSet: "
                    "'record_set_id' attribute is missing."
                )
            from synapseclient.models import RecordSet

            await RecordSet(id=self.task_properties.record_set_id).delete_async(
                synapse_client=synapse_client
            )

        else:
            raise ValueError(
                "'task_property' attribute is None. "
                "Deletion only supports FileBasedMetadataTaskProperties or "
                "RecordBasedMetadataTaskProperties."
            )

    await delete_curation_task(task_id=self.task_id, synapse_client=synapse_client)

store_async async

store_async(*, synapse_client: Optional[Synapse] = None) -> CurationTask

Creates a new CurationTask or updates an existing one on Synapse.

This method implements non-destructive updates. If a CurationTask with the same project_id and data_type exists and this instance hasn't been retrieved from Synapse before, it will merge the existing task data with the current instance before updating.

PARAMETER DESCRIPTION
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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
CurationTask

The CurationTask object.

TYPE: CurationTask

Create a new curation task asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask, FileBasedMetadataTaskProperties

syn = Synapse()
syn.login()

async def main():
    # Create file-based task properties
    file_properties = FileBasedMetadataTaskProperties(
        upload_folder_id="syn1234567",
        file_view_id="syn2345678"
    )

    # Create and store the curation task
    task = CurationTask(
        project_id="syn9876543",
        data_type="genomics_data",
        instructions="Upload your genomics files to the specified folder",
        task_properties=file_properties
    )
    task = await task.store_async()
    print(f"Created task with ID: {task.task_id}")

asyncio.run(main())
Source code in synapseclient/models/curation.py
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
async def store_async(
    self, *, synapse_client: Optional[Synapse] = None
) -> "CurationTask":
    """
    Creates a new CurationTask or updates an existing one on Synapse.

    This method implements non-destructive updates. If a CurationTask with the same
    project_id and data_type exists and this instance hasn't been retrieved from
    Synapse before, it will merge the existing task data with the current instance
    before updating.

    Arguments:
        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.

    Returns:
        CurationTask: The CurationTask object.

    Example: Create a new curation task asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask, FileBasedMetadataTaskProperties

        syn = Synapse()
        syn.login()

        async def main():
            # Create file-based task properties
            file_properties = FileBasedMetadataTaskProperties(
                upload_folder_id="syn1234567",
                file_view_id="syn2345678"
            )

            # Create and store the curation task
            task = CurationTask(
                project_id="syn9876543",
                data_type="genomics_data",
                instructions="Upload your genomics files to the specified folder",
                task_properties=file_properties
            )
            task = await task.store_async()
            print(f"Created task with ID: {task.task_id}")

        asyncio.run(main())
        ```
    """
    if not self.project_id:
        raise ValueError("project_id is required")
    if not self.data_type:
        raise ValueError("data_type is required")

    trace.get_current_span().set_attributes(
        {
            "synapse.data_type": self.data_type or "",
            "synapse.project_id": self.project_id or "",
            "synapse.task_id": str(self.task_id) if self.task_id else "",
        }
    )

    if (
        not self._last_persistent_instance
        and not self.task_id
        and (
            existing_task_id := await _get_existing_curation_task_id(
                project_id=self.project_id,
                data_type=self.data_type,
                synapse_client=synapse_client,
            )
        )
        and (
            existing_task := await CurationTask(task_id=existing_task_id).get_async(
                synapse_client=synapse_client
            )
        )
    ):
        merge_dataclass_entities(source=existing_task, destination=self)

    if self.task_id:
        task_result = await update_curation_task(
            task_id=self.task_id,
            curation_task=self.to_synapse_request(),
            synapse_client=synapse_client,
        )
        self.fill_from_dict(synapse_response=task_result)
        self._set_last_persistent_instance()
        return self
    else:
        if not self.project_id:
            raise ValueError("project_id is required to create a CurationTask")
        if not self.data_type:
            raise ValueError("data_type is required to create a CurationTask")
        if not self.instructions:
            raise ValueError("instructions is required to create a CurationTask")
        if not self.task_properties:
            raise ValueError("task_properties is required to create a CurationTask")

        task_result = await create_curation_task(
            curation_task=self.to_synapse_request(),
            synapse_client=synapse_client,
        )
        self.fill_from_dict(synapse_response=task_result)
        self._set_last_persistent_instance()
        return self

list_async async classmethod

list_async(project_id: str, *, assigned_to_me: Optional[bool] = None, assignee_ids: Optional[list[str]] = None, state_filter: Optional[list[Union[TaskState, str]]] = None, synapse_client: Optional[Synapse] = None) -> AsyncGenerator[CurationTask, None]

Generator that yields CurationTasks for a project as they become available.

PARAMETER DESCRIPTION
project_id

The synId of the project.

TYPE: str

assigned_to_me

When True, only return tasks assigned to the current user. Cannot be combined with assignee_ids. False does not mean "tasks not assigned to me". Defaults to None.

TYPE: Optional[bool] DEFAULT: None

assignee_ids

Optional list of principal IDs (users or teams) to filter tasks by assignee. Cannot be combined with assigned_to_me=True. Passing an empty list raises a ValueError; pass None to return tasks for any assignee. Defaults to None.

TYPE: Optional[list[str]] DEFAULT: None

state_filter

Optional list of TaskState values or exact-case strings to filter tasks by their current state (e.g., "IN_PROGRESS"). Defaults to None (all states returned). Passing an empty list raises a ValueError; pass None to return tasks in any state.

TYPE: Optional[list[Union[TaskState, str]]] DEFAULT: None

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.

TYPE: Optional[Synapse] DEFAULT: None

YIELDS DESCRIPTION
AsyncGenerator[CurationTask, None]

CurationTask objects as they are retrieved from the API.

RAISES DESCRIPTION
ValueError

If state_filter is an empty list.

ValueError

If assignee_ids is an empty list.

ValueError

If assigned_to_me is True and assignee_ids is also provided.

ValueError

If any value in state_filter is not a TaskState member or an exact-case string matching a TaskState value (e.g., "IN_PROGRESS").

ValueError

If the Synapse response for any task does not contain taskProperties.

List all curation tasks in a project asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask

syn = Synapse()
syn.login()

async def main():
    # List all curation tasks in the project
    async for task in CurationTask.list_async(project_id="syn9876543"):
        print(f"Task ID: {task.task_id}")
        print(f"Data Type: {task.data_type}")
        print(f"Instructions: {task.instructions}")
        print("---")

asyncio.run(main())
List only curation tasks assigned to the current user asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask

syn = Synapse()
syn.login()

async def main():
    async for task in CurationTask.list_async(
        project_id="syn9876543", assigned_to_me=True
    ):
        print(f"Task ID: {task.task_id}")
        print(f"Data Type: {task.data_type}")
        print("---")

asyncio.run(main())
List only in-progress curation tasks asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask, TaskState

syn = Synapse()
syn.login()

async def main():
    async for task in CurationTask.list_async(
        project_id="syn9876543",
        state_filter=[TaskState.IN_PROGRESS],
    ):
        print(f"Task ID: {task.task_id}")
        print(f"Data Type: {task.data_type}")
        print("---")

asyncio.run(main())
List only in-progress curation tasks using a string state filter asynchronously

 

state_filter also accepts plain strings matching TaskState names exactly.

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask

syn = Synapse()
syn.login()

async def main():
    async for task in CurationTask.list_async(
        project_id="syn9876543",
        state_filter=["IN_PROGRESS"],
    ):
        print(f"Task ID: {task.task_id}")
        print(f"Data Type: {task.data_type}")
        print("---")

asyncio.run(main())
Source code in synapseclient/models/curation.py
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
@skip_async_to_sync
@classmethod
async def list_async(
    cls,
    project_id: str,
    *,
    assigned_to_me: Optional[bool] = None,
    assignee_ids: Optional[list[str]] = None,
    state_filter: Optional[list[Union["TaskState", str]]] = None,
    synapse_client: Optional[Synapse] = None,
) -> AsyncGenerator["CurationTask", None]:
    """
    Generator that yields CurationTasks for a project as they become available.

    Arguments:
        project_id: The synId of the project.
        assigned_to_me: When True, only return tasks assigned to the current user.
            Cannot be combined with assignee_ids.
            False does not mean "tasks not assigned to me".
            Defaults to None.
        assignee_ids: Optional list of principal IDs (users or teams) to filter
            tasks by assignee. Cannot be combined with assigned_to_me=True.
            Passing an empty list raises a ValueError; pass None to return tasks
            for any assignee. Defaults to None.
        state_filter: Optional list of TaskState values or exact-case strings to
            filter tasks by their current state (e.g., "IN_PROGRESS"). Defaults to
            None (all states returned). Passing an empty list raises a ValueError;
            pass None to return tasks in any state.
        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.

    Yields:
        CurationTask objects as they are retrieved from the API.

    Raises:
        ValueError: If state_filter is an empty list.
        ValueError: If assignee_ids is an empty list.
        ValueError: If assigned_to_me is True and assignee_ids is also provided.
        ValueError: If any value in state_filter is not a TaskState member or
            an exact-case string matching a TaskState value (e.g., "IN_PROGRESS").
        ValueError: If the Synapse response for any task does not contain
            taskProperties.

    Example: List all curation tasks in a project asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask

        syn = Synapse()
        syn.login()

        async def main():
            # List all curation tasks in the project
            async for task in CurationTask.list_async(project_id="syn9876543"):
                print(f"Task ID: {task.task_id}")
                print(f"Data Type: {task.data_type}")
                print(f"Instructions: {task.instructions}")
                print("---")

        asyncio.run(main())
        ```

    Example: List only curation tasks assigned to the current user asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask

        syn = Synapse()
        syn.login()

        async def main():
            async for task in CurationTask.list_async(
                project_id="syn9876543", assigned_to_me=True
            ):
                print(f"Task ID: {task.task_id}")
                print(f"Data Type: {task.data_type}")
                print("---")

        asyncio.run(main())
        ```

    Example: List only in-progress curation tasks asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask, TaskState

        syn = Synapse()
        syn.login()

        async def main():
            async for task in CurationTask.list_async(
                project_id="syn9876543",
                state_filter=[TaskState.IN_PROGRESS],
            ):
                print(f"Task ID: {task.task_id}")
                print(f"Data Type: {task.data_type}")
                print("---")

        asyncio.run(main())
        ```

    Example: List only in-progress curation tasks using a string state filter asynchronously
         

        state_filter also accepts plain strings matching TaskState names exactly.

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask

        syn = Synapse()
        syn.login()

        async def main():
            async for task in CurationTask.list_async(
                project_id="syn9876543",
                state_filter=["IN_PROGRESS"],
            ):
                print(f"Task ID: {task.task_id}")
                print(f"Data Type: {task.data_type}")
                print("---")

        asyncio.run(main())
        ```
    """
    if state_filter == []:
        raise ValueError(
            "state_filter must not be empty. Pass None to return tasks in any state."
        )
    if assignee_ids == []:
        raise ValueError(
            "assignee_ids must not be empty. Pass None to return tasks for any assignee."
        )
    if assigned_to_me is True and assignee_ids is not None:
        raise ValueError(
            f"assigned_to_me and assignee_ids are mutually exclusive "
            f"and cannot be used together. Got assignee_ids={assignee_ids!r}."
        )

    if state_filter is not None:
        state_filter = coerce_enum_list(TaskState, state_filter)

    trace.get_current_span().set_attributes(
        {
            "synapse.project_id": project_id,
        }
    )

    async for task_dict in list_curation_tasks(
        project_id=project_id,
        assigned_to_me=assigned_to_me,
        assignee_ids=assignee_ids,
        state_filter=state_filter,
        synapse_client=synapse_client,
    ):
        task = cls().fill_from_dict(synapse_response=task_dict)
        yield task

create_grid_session_async async

create_grid_session_async(*, owner_principal_id: int | None = None, timeout: int = 120, synapse_client: Synapse | None = None) -> Grid

Create a new Grid session for this CurationTask and set it as the active session.

Picks the Grid seed from this task's task_properties:

  • RecordBasedMetadataTaskProperties uses record_set_id
  • FileBasedMetadataTaskProperties uses an initial_query that selects from the file_view_id

Always creates a new Grid session. To attach an existing session to a task, use set_active_grid_session_async instead.

The new session is created with the task's suggested_authorization_mode (from task_properties), which the server uses to determine access:

  • SESSION_OWNER: access is limited to the session owner (owner_principal_id, or the caller when not provided) and their team.
  • SOURCE_BENEFACTOR: access is inherited from the benefactor of the source entity (anyone with EDIT rights).
  • Unset (legacy): the caller becomes the owner.

After the Grid is created, updates the CurationTaskStatus to point its active_session_id at the new session. If that update fails for any reason, the newly created Grid is deleted on a best-effort basis and the original exception is re-raised.

PARAMETER DESCRIPTION
owner_principal_id

The principal ID (user or team) that will own the created grid session. When not provided, the principal ID of the caller is used.

TYPE: int | None DEFAULT: None

timeout

Seconds to wait for the grid creation job. Defaults to 120.

TYPE: int DEFAULT: 120

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.

TYPE: Synapse | None DEFAULT: None

RETURNS DESCRIPTION
Grid

The newly created Grid.

RAISES DESCRIPTION
ValueError

If task_id is unset or task_properties is of an unsupported type.

SynapseHTTPError

If the RecordSet or EntityView does not exist, or if the status update fails. The orphan Grid is deleted on a best-effort basis before the error is re-raised.

Create a grid session for a curation task asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask

syn = Synapse()
syn.login()

async def main():
    grid = await CurationTask(task_id=123).create_grid_session_async()
    print(grid.session_id)

asyncio.run(main())
Source code in synapseclient/models/curation.py
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
@otel_trace_method(
    method_to_trace_name=lambda self, **kwargs: (
        f"CurationTask_CreateGridSession: ID: {self.task_id}"
    )
)
async def create_grid_session_async(
    self,
    *,
    owner_principal_id: int | None = None,
    timeout: int = 120,
    synapse_client: Synapse | None = None,
) -> "Grid":
    """
    Create a new Grid session for this CurationTask and set it as the active session.

    Picks the Grid seed from this task's task_properties:

    - RecordBasedMetadataTaskProperties uses record_set_id
    - FileBasedMetadataTaskProperties uses an initial_query that selects from
      the file_view_id

    Always creates a new Grid session. To attach an existing session to a task,
    use set_active_grid_session_async instead.

    The new session is created with the task's suggested_authorization_mode
    (from task_properties), which the server uses to determine access:

    - SESSION_OWNER: access is limited to the session owner (owner_principal_id,
      or the caller when not provided) and their team.
    - SOURCE_BENEFACTOR: access is inherited from the benefactor of the source
      entity (anyone with EDIT rights).
    - Unset (legacy): the caller becomes the owner.

    After the Grid is created, updates the CurationTaskStatus to point its
    active_session_id at the new session. If that update fails for any reason,
    the newly created Grid is deleted on a best-effort basis and the original
    exception is re-raised.

    Arguments:
        owner_principal_id: The principal ID (user or team) that will own the
            created grid session. When not provided, the principal ID of the
            caller is used.
        timeout: Seconds to wait for the grid creation job. Defaults to 120.
        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.

    Returns:
        The newly created Grid.

    Raises:
        ValueError: If task_id is unset or task_properties is of an unsupported type.
        SynapseHTTPError: If the RecordSet or EntityView does not exist, or if the
            status update fails. The orphan Grid is deleted on a best-effort basis
            before the error is re-raised.

    Example: Create a grid session for a curation task asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask

        syn = Synapse()
        syn.login()

        async def main():
            grid = await CurationTask(task_id=123).create_grid_session_async()
            print(grid.session_id)

        asyncio.run(main())
        ```
    """
    if not self.task_id:
        raise ValueError(
            "task_id is required to create a CurationTask grid session"
        )

    if not self.task_properties:
        await self.get_async(synapse_client=synapse_client)

    if isinstance(self.task_properties, RecordBasedMetadataTaskProperties):
        if not self.task_properties.record_set_id:
            raise ValueError(
                "Cannot create grid session: "
                "task_properties.record_set_id is missing."
            )
        from synapseclient.models import RecordSet

        # raises SynapseHTTPError if RecordSet does not exist
        await RecordSet(id=self.task_properties.record_set_id).get_async(
            synapse_client=synapse_client
        )
        grid = Grid(
            record_set_id=self.task_properties.record_set_id,
            owner_principal_id=owner_principal_id,
            authorization_mode=self.task_properties.suggested_authorization_mode,
        )
    elif isinstance(self.task_properties, FileBasedMetadataTaskProperties):
        if not self.task_properties.file_view_id:
            raise ValueError(
                "Cannot create grid session: "
                "task_properties.file_view_id is missing."
            )
        from synapseclient.models import EntityView

        # raises SynapseHTTPError if EntityView does not exist
        await EntityView(id=self.task_properties.file_view_id).get_async(
            synapse_client=synapse_client
        )
        grid = Grid(
            initial_query=Query(
                sql=f"SELECT * FROM {self.task_properties.file_view_id}"
            ),
            owner_principal_id=owner_principal_id,
            authorization_mode=self.task_properties.suggested_authorization_mode,
        )
    else:
        raise ValueError(
            "task_properties must be a FileBasedMetadataTaskProperties or "
            "RecordBasedMetadataTaskProperties to create a grid session"
        )

    grid = await grid.create_async(
        timeout=timeout,
        synapse_client=synapse_client,
    )

    # Only one grid session can be set as the active one on a a given CurationTask
    # at a any time, though multiple sessions can exist.
    # If two users run this concurrently, one will lose the race and
    # receive a 412 (precondition failed). In that case — or if recording the
    # active session fails for any other reason — delete the session we just
    # created so it doesn't become an orphan. If the delete also fails, log a
    # warning so the caller knows manual cleanup is needed, then re-raise the
    # original exception in all cases.
    try:
        await self.set_active_grid_session_async(
            active_session_id=grid.session_id, synapse_client=synapse_client
        )
    except Exception:
        try:
            await grid.delete_async(synapse_client=synapse_client)
        except Exception:
            Synapse.get_client(synapse_client=synapse_client).logger.warning(
                "Failed to delete orphan grid session %s after status "
                "update failure; manual cleanup may be required.",
                grid.session_id,
            )
        raise

    return grid

set_task_state_async async

set_task_state_async(state: TaskState | str, *, synapse_client: Synapse | None = None) -> CurationTaskStatus

Set the state on this CurationTask's status.

Does not modify execution_details. Fetches the current CurationTaskStatus first so the update carries a fresh etag.

PARAMETER DESCRIPTION
state

The state to set on this task's status. Accepts a TaskState or a string exactly matching one of its members (e.g. NOT_STARTED, IN_PROGRESS, COMPLETED, CANCELED).

TYPE: TaskState | str

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.

TYPE: Synapse | None DEFAULT: None

RETURNS DESCRIPTION
CurationTaskStatus

The updated CurationTaskStatus object.

RAISES DESCRIPTION
ValueError

If the CurationTask object does not have a task_id, or if state is a string that does not match a TaskState member.

Mark a curation task as completed asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask, TaskState

syn = Synapse()
syn.login()

async def main():
    await CurationTask(task_id=123).set_task_state_async(
        state=TaskState.COMPLETED
    )

asyncio.run(main())
Mark a curation task as completed using a string asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask

syn = Synapse()
syn.login()

async def main():
    await CurationTask(task_id=123).set_task_state_async(
        state="COMPLETED"
    )

asyncio.run(main())
Source code in synapseclient/models/curation.py
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
@otel_trace_method(
    method_to_trace_name=lambda self, **kwargs: (
        f"CurationTask_SetTaskState: ID: {self.task_id}"
    )
)
async def set_task_state_async(
    self,
    state: "TaskState | str",
    *,
    synapse_client: Synapse | None = None,
) -> "CurationTaskStatus":
    """
    Set the state on this CurationTask's status.

    Does not modify execution_details. Fetches the current CurationTaskStatus
    first so the update carries a fresh etag.

    Arguments:
        state: The state to set on this task's status. Accepts a
            TaskState or a string exactly matching one of its members
            (e.g. NOT_STARTED, IN_PROGRESS, COMPLETED, CANCELED).
        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.

    Returns:
        The updated CurationTaskStatus object.

    Raises:
        ValueError: If the CurationTask object does not have a task_id, or
            if state is a string that does not match a TaskState member.

    Example: Mark a curation task as completed asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask, TaskState

        syn = Synapse()
        syn.login()

        async def main():
            await CurationTask(task_id=123).set_task_state_async(
                state=TaskState.COMPLETED
            )

        asyncio.run(main())
        ```

    Example: Mark a curation task as completed using a string asynchronously
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask

        syn = Synapse()
        syn.login()

        async def main():
            await CurationTask(task_id=123).set_task_state_async(
                state="COMPLETED"
            )

        asyncio.run(main())
        ```
    """
    try:
        coerced_state = TaskState(state)
    except ValueError as exc:
        raise ValueError(
            f"{state!r} is not a valid TaskState. "
            f"Expected one of: {[s.value for s in TaskState]}."
        ) from exc

    status = await self.get_status_async(synapse_client=synapse_client)
    status.state = coerced_state
    return await self.update_status_async(
        curation_task_status=status, synapse_client=synapse_client
    )

synchronize_active_grid_session_async async

synchronize_active_grid_session_async(*, sync_type: Union[SyncType, str], synapse_client: Optional[Synapse] = None) -> Optional[Grid]

Synchronize this task's active grid session against its source entity.

If task_properties is not yet populated on this object, it is fetched from Synapse first. If the task has no active grid session, a warning is logged and None is returned; no new grid session is created.

sync_type is always required, for both task types. FileBasedMetadataTaskProperties tasks always perform a SyncType.PULL_PUSH regardless of the value passed in.

PARAMETER DESCRIPTION
sync_type

The type of synchronization to perform. Required.

  • SyncType.PULL: Update the grid session with the latest data/schema from the source RecordSet, without writing the grid back to it. Use this to preview an incoming schema or data change in the grid before committing it. Only supported for record-based tasks.
  • SyncType.PULL_PUSH: Update the grid session with the latest data from the source, then write the grid's data back to the source (the source RecordSet for record-based tasks, or the referenced entities for file-based tasks). This commits any in-progress curation in the grid as a new version of the source.

For record-based tasks, this determines whether the call previews (PULL) or commits (PULL_PUSH). For file-based tasks, the value is ignored and the call always behaves as SyncType.PULL_PUSH.

TYPE: Union[SyncType, str]

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
Optional[Grid]

The synchronized Grid, or None if the task has no active grid session.

RAISES DESCRIPTION
ValueError

If task_id is unset, task_properties is of an unsupported type, or sync_type is not provided for a record-based task.

Synchronize a record-based curation task's grid session

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask
from synapseclient.models.curation import SyncType

syn = Synapse()
syn.login()

async def main():
    grid = await CurationTask(task_id=123).synchronize_active_grid_session_async(
        sync_type=SyncType.PULL_PUSH
    )
    if grid is not None:
        print(grid.session_id)

asyncio.run(main())
Synchronize a file-based curation task's grid session

 

File-based tasks always synchronize with SyncType.PULL_PUSH, so any value works here -- but sync_type still has to be passed.

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask
from synapseclient.models.curation import SyncType

syn = Synapse()
syn.login()

async def main():
    grid = await CurationTask(task_id=456).synchronize_active_grid_session_async(
        sync_type=SyncType.PULL_PUSH
    )
    if grid is not None:
        print(grid.session_id)

asyncio.run(main())
Source code in synapseclient/models/curation.py
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
@otel_trace_method(
    method_to_trace_name=lambda self, **kwargs: (
        f"CurationTask_SynchronizeActiveGridSession: ID: {self.task_id}"
    )
)
async def synchronize_active_grid_session_async(
    self,
    *,
    sync_type: Union["SyncType", str],
    synapse_client: Optional[Synapse] = None,
) -> Optional["Grid"]:
    """
    Synchronize this task's active grid session against its source entity.

    If task_properties is not yet populated on this object, it is fetched
    from Synapse first. If the task has no active grid session, a warning
    is logged and None is returned; no new grid session is created.

    `sync_type` is always required, for both task types. FileBasedMetadataTaskProperties
    tasks always perform a SyncType.PULL_PUSH regardless of the value passed in.

    Arguments:
        sync_type: The type of synchronization to perform. Required.

            - SyncType.PULL: Update the grid session with the latest data/schema
              from the source RecordSet, without writing the grid back to it.
              Use this to preview an incoming schema or data change in the grid
              before committing it. Only supported for record-based tasks.
            - SyncType.PULL_PUSH: Update the grid session with the latest data
              from the source, then write the grid's data back to the source
              (the source RecordSet for record-based tasks, or the referenced
              entities for file-based tasks). This commits any in-progress
              curation in the grid as a new version of the source.

            For record-based tasks, this determines whether the call previews
            (PULL) or commits (PULL_PUSH). For file-based tasks, the value is
            ignored and the call always behaves as SyncType.PULL_PUSH.
        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.

    Returns:
        The synchronized Grid, or None if the task has no active grid session.

    Raises:
        ValueError: If task_id is unset, task_properties is of an unsupported
            type, or sync_type is not provided for a record-based task.

    Example: Synchronize a record-based curation task's grid session
         

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask
        from synapseclient.models.curation import SyncType

        syn = Synapse()
        syn.login()

        async def main():
            grid = await CurationTask(task_id=123).synchronize_active_grid_session_async(
                sync_type=SyncType.PULL_PUSH
            )
            if grid is not None:
                print(grid.session_id)

        asyncio.run(main())
        ```

    Example: Synchronize a file-based curation task's grid session
         

        File-based tasks always synchronize with SyncType.PULL_PUSH, so any
        value works here -- but `sync_type` still has to be passed.

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask
        from synapseclient.models.curation import SyncType

        syn = Synapse()
        syn.login()

        async def main():
            grid = await CurationTask(task_id=456).synchronize_active_grid_session_async(
                sync_type=SyncType.PULL_PUSH
            )
            if grid is not None:
                print(grid.session_id)

        asyncio.run(main())
        ```
    """
    client = Synapse.get_client(synapse_client=synapse_client)

    if not self.task_properties:
        await self.get_async(synapse_client=synapse_client)

    if isinstance(self.task_properties, FileBasedMetadataTaskProperties):
        if sync_type is not None and sync_type != SyncType.PULL_PUSH:
            client.logger.warning(
                f"Ignoring sync_type={sync_type} for CurationTask "
                f"{self.task_id}: FileBasedMetadataTaskProperties tasks always "
                "use SyncType.PULL_PUSH."
            )
        sync_type = SyncType.PULL_PUSH
    elif isinstance(self.task_properties, RecordBasedMetadataTaskProperties):
        if not sync_type:
            raise ValueError(
                "sync_type must be provided for RecordBasedMetadataTaskProperties"
            )
    else:
        raise ValueError(
            f"Synchronization only supports FileBasedMetadataTaskProperties or "
            f"RecordBasedMetadataTaskProperties, got {type(self.task_properties).__name__}."
        )

    status = await self.get_status_async(synapse_client=synapse_client)
    if (
        status.execution_details is None
        or status.execution_details.active_session_id is None
    ):
        client.logger.warning(
            f"No active grid session found for task {self.task_id}. Skipping "
            "synchronization."
        )
        return None
    active_grid_session_id = status.execution_details.active_session_id

    client.logger.info(
        f"Synchronizing active grid session {active_grid_session_id} for "
        f"task {self.task_id}"
    )
    grid = Grid(session_id=active_grid_session_id)
    return await grid.synchronize_async(
        synapse_client=synapse_client, sync_type=sync_type
    )

synapseclient.models.RecordSet dataclass

Bases: RecordSetSynchronousProtocol, AccessControllable, BaseJSONSchema

A RecordSet entity captures record-based metadata as a special type of CSV. The record set content can be curated using the grid services. When a grid is created from a record set, its data can be exported back to a new version of the record set. The export will include the validation summary as well as a validation file handle that contains detailed validation results for each row in the record set.

ATTRIBUTE DESCRIPTION
id

The unique immutable ID for this file. A new ID will be generated for new Files. Once issued, this ID is guaranteed to never change or be re-issued.

TYPE: Optional[str]

name

The name of this entity. Must be 256 characters or less. Names may only contain: letters, numbers, spaces, underscores, hyphens, periods, plus signs, apostrophes, and parentheses. If not specified, the name will be derived from the file name.

TYPE: Optional[str]

path

The path to the file on disk. Using shorthand ~ will be expanded to the user's home directory.

This is used during a get operation to specify where to download the file to. It should be pointing to a directory.

This is also used during a store operation to specify the file to upload. It should be pointing to a file.

TYPE: Optional[str]

description

The description of this file. Must be 1000 characters or less.

TYPE: Optional[str]

parent_id

The ID of the Entity that is the parent of this Entity. Setting this to a new value and storing it will move this File under the new parent.

TYPE: Optional[str]

version_label

The version label for this entity. Updates to the entity will increment the version number.

TYPE: Optional[str]

version_comment

The version comment for this entity.

TYPE: Optional[str]

data_file_handle_id

ID of the file handle associated with this entity. You may define an existing data_file_handle_id to use the existing data_file_handle_id. The creator of the file must also be the owner of the data_file_handle_id to have permission to store the file.

TYPE: Optional[str]

activity

The Activity model represents the main record of Provenance in Synapse. It is analygous to the Activity defined in the W3C Specification on Provenance. Activity cannot be removed during a store operation by setting it to None. You must use: synapseclient.models.Activity.delete_async or synapseclient.models.Activity.disassociate_from_entity_async.

TYPE: Optional[Activity]

annotations

Additional metadata associated with the entity. The key is the name of your desired annotations. The value is an object containing a list of values (use empty list to represent no values for key) and the value type associated with all values in the list. To remove all annotations set this to an empty dict {}.

TYPE: Optional[Dict[str, Union[List[str], List[bool], List[float], List[int], List[date], List[datetime]]]]

upsert_keys

One or more column names that define this upsert key for this set. This key is used to determine if a new record should be treated as an update or an insert.

TYPE: Optional[List[str]]

csv_descriptor

The description of a CSV for upload or download.

TYPE: Optional[CsvTableDescriptor]

validation_summary

Summary statistics for the JSON schema validation results for the children of an Entity container (Project or Folder).

TYPE: Optional[ValidationSummary]

file_name_override

An optional replacement for the name of the uploaded file. This is distinct from the entity name. If omitted the file will retain its original name.

TYPE: Optional[str]

validation_file_handle_id

(Read Only) Pointer to a CSV file that contains the detailed validation results for each row in the record set. The CSV file will contain for each row the following columns: row_index, is_valid, validation_error_message, all_validation_messages. Generated only from a grid session export, cannot be changed by the user.

TYPE: Optional[str]

content_type

(New Upload Only) Used to manually specify Content-type header, for example 'application/png' or 'application/json; charset=UTF-8'. If not specified, the content type will be derived from the file extension.

This can be specified only during the initial store of this file. In order to change this after the File has been created use synapseclient.models.File.change_metadata.

TYPE: Optional[str]

content_size

(New Upload Only) The size of the file in bytes. This can be specified only during the initial creation of the File. This is also only applicable to files not uploaded to Synapse. ie: synapse_store is False.

TYPE: Optional[int]

content_md5

(Store only) The MD5 of the file is known. If not supplied this will be computed in the client is possible. If supplied for a file entity already stored in Synapse it will be calculated again to check if a new upload needs to occur. This will not be filled in during a read for data. It is only used during a store operation. To retrieve the md5 of the file after read from synapse use the .file_handle.content_md5 attribute.

TYPE: Optional[str]

create_or_update

(Store only) Indicates whether the method should automatically perform an update if the file conflicts with an existing Synapse object.

TYPE: bool

force_version

(Store only) Indicates whether the method should increment the version of the object if something within the entity has changed. For example updating the description or name. You may set this to False and an update to the entity will not increment the version.

Updating the version_label attribute will also cause a version update regardless of this flag.

An update to the MD5 of the file will force a version update regardless of this flag.

TYPE: bool

is_restricted

(Store only) If set to true, an email will be sent to the Synapse access control team to start the process of adding terms-of-use or review board approval for this entity. You will be contacted with regards to the specific data being restricted and the requirements of access.

This may be used only by an administrator of the specified file.

TYPE: bool

merge_existing_annotations

(Store only) Works in conjunction with create_or_update in that this is only evaluated if create_or_update is True. If this entity exists in Synapse that has annotations that are not present in a store operation, these annotations will be added to the entity. If this is False any annotations that are not present within a store operation will be removed from this entity. This allows one to complete a destructive update of annotations on an entity.

TYPE: bool

associate_activity_to_new_version

(Store only) Works in conjunction with create_or_update in that this is only evaluated if create_or_update is True. When true an activity already attached to the current version of this entity will be associated the new version during a store operation if the version was updated. This is useful if you are updating the entity and want to ensure that the activity is persisted onto the new version the entity.

When this is False the activity will not be associated to the new version of the entity during a store operation.

Regardless of this setting, if you have an Activity object on the entity it will be persisted onto the new version. This is only used when you don't have an Activity object on the entity.

TYPE: bool

synapse_store

(Store only) Whether the File should be uploaded or if false: only the path should be stored when synapseclient.models.File.store is called.

TYPE: bool

download_file

(Get only) If True the file will be downloaded.

TYPE: bool

if_collision

(Get only) Determines how to handle file collisions. Defaults to "keep.both". May be:

  • overwrite.local
  • keep.local
  • keep.both

TYPE: str

synapse_container_limit

(Get only) A Synanpse ID used to limit the search in Synapse if file is specified as a local file. That is, if the file is stored in multiple locations in Synapse only the ones in the specified folder/project will be returned.

TYPE: Optional[str]

etag

(Read Only) Synapse employs an Optimistic Concurrency Control (OCC) scheme to handle concurrent updates. Since the E-Tag changes every time an entity is updated it is used to detect when a client's current representation of an entity is out-of-date.

TYPE: Optional[str]

created_on

(Read Only) The date this entity was created.

TYPE: Optional[str]

modified_on

(Read Only) The date this entity was last modified.

TYPE: Optional[str]

created_by

(Read Only) The ID of the user that created this entity.

TYPE: Optional[str]

modified_by

(Read Only) The ID of the user that last modified this entity.

TYPE: Optional[str]

version_number

(Read Only) The version number issued to this version on the object.

TYPE: Optional[int]

is_latest_version

(Read Only) If this is the latest version of the object.

TYPE: Optional[bool]

file_handle

(Read Only) The file handle associated with this entity.

TYPE: Optional[FileHandle]

Source code in synapseclient/models/recordset.py
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
@dataclass()
@async_to_sync
class RecordSet(RecordSetSynchronousProtocol, AccessControllable, BaseJSONSchema):
    """
    A RecordSet entity captures record-based metadata as a special type of CSV.
    The record set content can be curated using the grid services.
    When a grid is created from a record set, its data can be exported back to a new version of the record set.
    The export will include the validation summary as well as a validation file handle that
      contains detailed validation results for each row in the record set.

    Attributes:
        id: The unique immutable ID for this file. A new ID will be generated for new
            Files. Once issued, this ID is guaranteed to never change or be re-issued.
        name: The name of this entity. Must be 256 characters or less. Names may only
            contain: letters, numbers, spaces, underscores, hyphens, periods, plus
            signs, apostrophes, and parentheses. If not specified, the name will be
            derived from the file name.
        path: The path to the file on disk. Using shorthand `~` will be expanded to
            the user's home directory.

            This is used during a `get` operation to specify where to download the
            file to. It should be pointing to a directory.

            This is also used during a `store` operation to specify the file to
            upload. It should be pointing to a file.
        description: The description of this file. Must be 1000 characters or less.
        parent_id: The ID of the Entity that is the parent of this Entity. Setting
            this to a new value and storing it will move this File under the new
            parent.
        version_label: The version label for this entity. Updates to the entity will
            increment the version number.
        version_comment: The version comment for this entity.
        data_file_handle_id: ID of the file handle associated with this entity. You
            may define an existing data_file_handle_id to use the existing
            data_file_handle_id. The creator of the file must also be the owner of
            the data_file_handle_id to have permission to store the file.
        activity: The Activity model represents the main record of Provenance in
            Synapse.  It is analygous to the Activity defined in the
            [W3C Specification](https://www.w3.org/TR/prov-n/) on Provenance.
            Activity cannot be removed during a store operation by setting it to None.
            You must use: [synapseclient.models.Activity.delete_async][] or
            [synapseclient.models.Activity.disassociate_from_entity_async][].
        annotations: Additional metadata associated with the entity. The key is the
            name of your desired annotations. The value is an object containing a list
            of values (use empty list to represent no values for key) and the value
            type associated with all values in the list. To remove all annotations
            set this to an empty dict `{}`.
        upsert_keys: One or more column names that define this upsert key for this
            set. This key is used to determine if a new record should be treated as
            an update or an insert.
        csv_descriptor: The description of a CSV for upload or download.
        validation_summary: Summary statistics for the JSON schema validation results
            for the children of an Entity container (Project or Folder).
        file_name_override: An optional replacement for the name of the uploaded
            file. This is distinct from the entity name. If omitted the file will
            retain its original name.
        validation_file_handle_id: (Read Only) Pointer to a CSV file that contains the
            detailed validation results for each row in the record set. The CSV file
            will contain for each row the following columns: row_index, is_valid,
            validation_error_message, all_validation_messages. Generated only from a
            grid session export, cannot be changed by the user.
        content_type: (New Upload Only) Used to manually specify Content-type header,
            for example 'application/png' or 'application/json; charset=UTF-8'. If not
            specified, the content type will be derived from the file extension.

            This can be specified only during the initial store of this file. In order
            to change this after the File has been created use
            [synapseclient.models.File.change_metadata][].
        content_size: (New Upload Only) The size of the file in bytes. This can be
            specified only during the initial creation of the File. This is also only
            applicable to files not uploaded to Synapse. ie: `synapse_store` is False.
        content_md5: (Store only) The MD5 of the file is known. If not supplied this
            will be computed in the client is possible. If supplied for a file entity
            already stored in Synapse it will be calculated again to check if a new
            upload needs to occur. This will not be filled in during a read for data.
            It is only used during a store operation. To retrieve the md5 of the file
            after read from synapse use the `.file_handle.content_md5` attribute.
        create_or_update: (Store only) Indicates whether the method should
            automatically perform an update if the file conflicts with an existing
            Synapse object.
        force_version: (Store only) Indicates whether the method should increment the
            version of the object if something within the entity has changed. For
            example updating the description or name. You may set this to False and
            an update to the entity will not increment the version.

            Updating the `version_label` attribute will also cause a version update
            regardless of this flag.

            An update to the MD5 of the file will force a version update regardless
            of this flag.
        is_restricted: (Store only) If set to true, an email will be sent to the
            Synapse access control team to start the process of adding terms-of-use
            or review board approval for this entity. You will be contacted with
            regards to the specific data being restricted and the requirements of
            access.

            This may be used only by an administrator of the specified file.
        merge_existing_annotations: (Store only) Works in conjunction with
            `create_or_update` in that this is only evaluated if `create_or_update`
            is True. If this entity exists in Synapse that has annotations that are
            not present in a store operation, these annotations will be added to the
            entity. If this is False any annotations that are not present within a
            store operation will be removed from this entity. This allows one to
            complete a destructive update of annotations on an entity.
        associate_activity_to_new_version: (Store only) Works in conjunction with
            `create_or_update` in that this is only evaluated if `create_or_update`
            is True. When true an activity already attached to the current version of
            this entity will be associated the new version during a store operation
            if the version was updated. This is useful if you are updating the entity
            and want to ensure that the activity is persisted onto the new version
            the entity.

            When this is False the activity will not be associated to the new version
            of the entity during a store operation.

            Regardless of this setting, if you have an Activity object on the entity
            it will be persisted onto the new version. This is only used when you
            don't have an Activity object on the entity.
        synapse_store: (Store only) Whether the File should be uploaded or if false:
            only the path should be stored when [synapseclient.models.File.store][]
            is called.
        download_file: (Get only) If True the file will be downloaded.
        if_collision: (Get only) Determines how to handle file collisions. Defaults
            to "keep.both". May be:

            - `overwrite.local`
            - `keep.local`
            - `keep.both`
        synapse_container_limit: (Get only) A Synanpse ID used to limit the search in
            Synapse if file is specified as a local file. That is, if the file is
            stored in multiple locations in Synapse only the ones in the specified
            folder/project will be returned.
        etag: (Read Only) Synapse employs an Optimistic Concurrency Control (OCC)
            scheme to handle concurrent updates. Since the E-Tag changes every time
            an entity is updated it is used to detect when a client's current
            representation of an entity is out-of-date.
        created_on: (Read Only) The date this entity was created.
        modified_on: (Read Only) The date this entity was last modified.
        created_by: (Read Only) The ID of the user that created this entity.
        modified_by: (Read Only) The ID of the user that last modified this entity.
        version_number: (Read Only) The version number issued to this version on the
            object.
        is_latest_version: (Read Only) If this is the latest version of the object.
        file_handle: (Read Only) The file handle associated with this entity.
    """

    id: Optional[str] = None
    """The unique immutable ID for this file. A new ID will be generated for new Files.
    Once issued, this ID is guaranteed to never change or be re-issued."""

    name: Optional[str] = None
    """
    The name of this entity. Must be 256 characters or less.
    Names may only contain: letters, numbers, spaces, underscores, hyphens, periods,
    plus signs, apostrophes, and parentheses. If not specified, the name will be
    derived from the file name.
    """

    path: Optional[str] = field(default=None, compare=False)
    """The path to the file on disk. Using shorthand `~` will be expanded to the user's
    home directory.

    This is used during a `get` operation to specify where to download the file to. It
    should be pointing to a directory.

    This is also used during a `store` operation to specify the file to upload. It
    should be pointing to a file."""

    description: Optional[str] = None
    """The description of this file. Must be 1000 characters or less."""

    parent_id: Optional[str] = None
    """The ID of the Entity that is the parent of this Entity. Setting this to a new
    value and storing it will move this File under the new parent."""

    version_label: Optional[str] = None
    """The version label for this entity. Updates to the entity will increment the
    version number."""

    version_comment: Optional[str] = None
    """The version comment for this entity."""

    data_file_handle_id: Optional[str] = None
    """
    ID of the file handle associated with this entity. You may define an existing
    data_file_handle_id to use the existing data_file_handle_id. The creator of the
    file must also be the owner of the data_file_handle_id to have permission to
    store the file.
    """

    activity: Optional[Activity] = field(default=None, compare=False)
    """The Activity model represents the main record of Provenance in Synapse.  It is
    analygous to the Activity defined in the
    [W3C Specification](https://www.w3.org/TR/prov-n/) on Provenance. Activity cannot
    be removed during a store operation by setting it to None. You must use:
    [synapseclient.models.Activity.delete_async][] or
    [synapseclient.models.Activity.disassociate_from_entity_async][].
    """

    annotations: Optional[
        Dict[
            str,
            Union[
                List[str],
                List[bool],
                List[float],
                List[int],
                List[date],
                List[datetime],
            ],
        ]
    ] = field(default_factory=dict, compare=False)
    """Additional metadata associated with the folder. The key is the name of your
    desired annotations. The value is an object containing a list of values
    (use empty list to represent no values for key) and the value type associated with
    all values in the list. To remove all annotations set this to an empty dict `{}`."""

    upsert_keys: Optional[List[str]] = field(default_factory=list)
    """One or more column names that define this upsert key for this set. This key is
    used to determine if a new record should be treated as an update or an insert.
    """

    csv_descriptor: Optional["CsvTableDescriptor"] = field(default=None)
    """The description of a CSV for upload or download."""

    validation_summary: Optional[ValidationSummary] = field(default=None, compare=False)
    """Summary statistics for the JSON schema validation results for the children of
    an Entity container (Project or Folder)"""

    file_name_override: Optional[str] = None
    """An optional replacement for the name of the uploaded file. This is distinct from
    the entity name. If omitted the file will retain its original name.
    """

    validation_file_handle_id: Optional[str] = None
    """
    (Read Only) Pointer to a CSV file that contains the detailed validation results for
    each row in the record set. The CSV file will contain for each row the following
    columns: row_index, is_valid, validation_error_message, all_validation_messages.
    Generated only from a grid session export, cannot be changed by the user.
    """

    content_type: Optional[str] = None
    """
    (New Upload Only)
    Used to manually specify Content-type header, for example 'application/png'
    or 'application/json; charset=UTF-8'. If not specified, the content type will be
    derived from the file extension.

    This can be specified only during the initial store of this file. In order to change
    this after the File has been created use
    [synapseclient.models.File.change_metadata][].
    """

    content_size: Optional[int] = None
    """
    (New Upload Only)
    The size of the file in bytes. This can be specified only during the initial
    creation of the File. This is also only applicable to files not uploaded to Synapse.
    ie: `synapse_store` is False.
    """

    content_md5: Optional[str] = field(default=None, compare=False)
    """
    (Store only)
    The MD5 of the file is known. If not supplied this will be computed in the client
    is possible. If supplied for a file entity already stored in Synapse it will be
    calculated again to check if a new upload needs to occur. This will not be filled
    in during a read for data. It is only used during a store operation. To retrieve
    the md5 of the file after read from synapse use the `.file_handle.content_md5`
    attribute.
    """

    create_or_update: bool = field(default=True, repr=False, compare=False)
    """
    (Store only)

    Indicates whether the method should automatically perform an update if the file
    conflicts with an existing Synapse object.
    """

    force_version: bool = field(default=True, repr=False, compare=False)
    """
    (Store only)

    Indicates whether the method should increment the version of the object if something
    within the entity has changed. For example updating the description or name.
    You may set this to False and an update to the entity will not increment the
    version.

    Updating the `version_label` attribute will also cause a version update regardless
    of this flag.

    An update to the MD5 of the file will force a version update regardless of this
    flag.
    """

    is_restricted: bool = field(default=False, repr=False)
    """
    (Store only)

    If set to true, an email will be sent to the Synapse access control team to start
    the process of adding terms-of-use or review board approval for this entity.
    You will be contacted with regards to the specific data being restricted and the
    requirements of access.

    This may be used only by an administrator of the specified file.
    """

    merge_existing_annotations: bool = field(default=True, repr=False, compare=False)
    """
    (Store only)

    Works in conjunction with `create_or_update` in that this is only evaluated if
    `create_or_update` is True. If this entity exists in Synapse that has annotations
    that are not present in a store operation, these annotations will be added to the
    entity. If this is False any annotations that are not present within a store
    operation will be removed from this entity. This allows one to complete a
    destructive update of annotations on an entity.
    """

    associate_activity_to_new_version: bool = field(
        default=False, repr=False, compare=False
    )
    """
    (Store only)

    Works in conjunction with `create_or_update` in that this is only evaluated if
    `create_or_update` is True. When true an activity already attached to the current
    version of this entity will be associated the new version during a store operation
    if the version was updated. This is useful if you are updating the entity and want
    to ensure that the activity is persisted onto the new version the entity.

    When this is False the activity will not be associated to the new version of the
    entity during a store operation.

    Regardless of this setting, if you have an Activity object on the entity it will be
    persisted onto the new version. This is only used when you don't have an Activity
    object on the entity.
    """

    synapse_store: bool = field(default=True, repr=False)
    """
    (Store only)

    Whether the File should be uploaded or if false: only the path should be stored when
    [synapseclient.models.File.store][] is called.
    """

    download_file: bool = field(default=True, repr=False, compare=False)
    """
    (Get only)

    If True the file will be downloaded."""

    if_collision: str = field(default="keep.both", repr=False, compare=False)
    """
    (Get only)

    Determines how to handle file collisions. Defaults to "keep.both".
            May be

            - `overwrite.local`
            - `keep.local`
            - `keep.both`
    """

    synapse_container_limit: Optional[str] = field(
        default=None, repr=False, compare=False
    )
    """A Synanpse ID used to limit the search in Synapse if file is specified as a local
    file. That is, if the file is stored in multiple locations in Synapse only the
    ones in the specified folder/project will be returned."""

    etag: Optional[str] = field(default=None, compare=False)
    """
    (Read Only)
    Synapse employs an Optimistic Concurrency Control (OCC) scheme to handle
    concurrent updates. Since the E-Tag changes every time an entity is updated it is
    used to detect when a client's current representation of an entity is out-of-date.
    """

    created_on: Optional[str] = field(default=None, compare=False)
    """(Read Only) The date this entity was created."""

    modified_on: Optional[str] = field(default=None, compare=False)
    """(Read Only) The date this entity was last modified."""

    created_by: Optional[str] = field(default=None, compare=False)
    """(Read Only) The ID of the user that created this entity."""

    modified_by: Optional[str] = field(default=None, compare=False)
    """(Read Only) The ID of the user that last modified this entity."""

    version_number: Optional[int] = field(default=None, compare=False)
    """(Read Only) The version number issued to this version on the object."""

    is_latest_version: Optional[bool] = field(default=None, compare=False)
    """(Read Only) If this is the latest version of the object."""

    file_handle: Optional["FileHandle"] = field(default=None, compare=False)
    """(Read Only) The file handle associated with this entity."""

    _last_persistent_instance: Optional["RecordSet"] = field(
        default=None, repr=False, compare=False
    )
    """The last persistent instance of this object. This is used to determine if the
    object has been changed and needs to be updated in Synapse."""

    @property
    def has_changed(self) -> bool:
        """
        Determines if the object has been changed and needs to be updated in Synapse."""
        return (
            not self._last_persistent_instance or self._last_persistent_instance != self
        )

    def _set_last_persistent_instance(self) -> None:
        """Stash the last time this object interacted with Synapse. This is used to
        determine if the object has been changed and needs to be updated in Synapse."""
        del self._last_persistent_instance
        self._last_persistent_instance = dataclasses.replace(self)
        self._last_persistent_instance.activity = (
            dataclasses.replace(self.activity) if self.activity else None
        )
        self._last_persistent_instance.annotations = (
            deepcopy(self.annotations) if self.annotations else {}
        )

    def _fill_from_file_handle(self) -> None:
        """Fill the file object from the file handle."""
        if self.file_handle:
            self.data_file_handle_id = self.file_handle.id
            self.content_type = self.file_handle.content_type
            self.content_size = self.file_handle.content_size

    def fill_from_dict(
        self,
        entity: Dict[str, Union[bool, str, int]],
        set_annotations: bool = True,
    ) -> "RecordSet":
        """
        Converts a response from the REST API into this dataclass.

        This method populates the RecordSet instance with data from a Synapse REST API
        response or a dictionary containing RecordSet information. It handles the
        conversion from Synapse API field names (camelCase) to Python attribute names
        (snake_case) and processes nested objects appropriately.

        Arguments:
            synapse_file: The response from the REST API or a dictionary containing
                RecordSet data. Can be either a Synapse_File object or a dictionary
                with string keys and various value types.
            set_annotations: Whether to set the annotations from the response.
                If True, annotations will be populated from the API response.

        Returns:
            The RecordSet object with updated attributes from the API response.
        """
        self.id = entity.get("id", None)
        self.name = entity.get("name", None)
        self.description = entity.get("description", None)
        self.etag = entity.get("etag", None)
        self.created_on = entity.get("createdOn", None)
        self.modified_on = entity.get("modifiedOn", None)
        self.created_by = entity.get("createdBy", None)
        self.modified_by = entity.get("modifiedBy", None)
        self.parent_id = entity.get("parentId", None)
        self.version_number = entity.get("versionNumber", None)
        self.version_label = entity.get("versionLabel", None)
        self.version_comment = entity.get("versionComment", None)
        self.is_latest_version = entity.get("isLatestVersion", False)
        self.data_file_handle_id = entity.get("dataFileHandleId", None)
        self.path = entity.get("path", self.path)
        self.file_name_override = entity.get("fileNameOverride", None)
        self.validation_file_handle_id = entity.get("validationFileHandleId", None)
        csv_descriptor = entity.get("csvDescriptor", None)
        if csv_descriptor:
            from synapseclient.models import CsvTableDescriptor

            self.csv_descriptor = CsvTableDescriptor().fill_from_dict(csv_descriptor)

        validation_summary = entity.get("validationSummary", None)

        if validation_summary:
            self.validation_summary = ValidationSummary(
                container_id=validation_summary.get("containerId", None),
                total_number_of_children=validation_summary.get(
                    "totalNumberOfChildren", None
                ),
                number_of_valid_children=validation_summary.get(
                    "numberOfValidChildren", None
                ),
                number_of_invalid_children=validation_summary.get(
                    "numberOfInvalidChildren", None
                ),
                number_of_unknown_children=validation_summary.get(
                    "numberOfUnknownChildren", None
                ),
                generated_on=validation_summary.get("generatedOn", None),
            )

        self.upsert_keys = entity.get("upsertKey", [])

        synapse_file_handle = entity.get("_file_handle", None)
        if synapse_file_handle:
            from synapseclient.models import FileHandle

            file_handle = self.file_handle or FileHandle()
            self.file_handle = file_handle.fill_from_dict(
                synapse_instance=synapse_file_handle
            )
            self._fill_from_file_handle()

        if set_annotations:
            self.annotations = Annotations.from_dict(entity.get("annotations", {}))
        return self

    def _cannot_store(self) -> bool:
        """
        Determines based on guard conditions if a store operation can proceed.
        """
        return (
            not (
                self.id is not None
                and (self.path is not None or self.data_file_handle_id is not None)
            )
            and not (self.path is not None and self.parent_id is not None)
            and not (
                self.parent_id is not None and self.data_file_handle_id is not None
            )
        )

    async def _load_local_md5(self) -> None:
        """
        Load the MD5 hash of a local file if it exists and hasn't been loaded yet.

        This method computes and sets the content_md5 attribute for local files
        that exist on disk. It only performs the calculation if the content_md5
        is not already set and the path points to an existing file.
        """
        if not self.content_md5 and self.path and os.path.isfile(self.path):
            self.content_md5 = utils.md5_for_file_hex(filename=self.path)

    async def _find_existing_entity(
        self, *, synapse_client: Optional[Synapse] = None
    ) -> Union["RecordSet", None]:
        """
        Determines if the RecordSet already exists in Synapse.

        This method searches for an existing RecordSet in Synapse that matches the
        current instance. If found, it returns the existing RecordSet object, otherwise
        it returns None. This is used to determine if the RecordSet should be updated
        or created during a store operation.

        Arguments:
            synapse_client: If not passed in and caching was not disabled, this will
                use the last created instance from the Synapse class constructor.

        Returns:
            The existing RecordSet object if it exists in Synapse, None otherwise.
        """

        async def get_entity(existing_id: str) -> "RecordSet":
            """Small wrapper to retrieve a file instance without raising an error if it
            does not exist.

            Arguments:
                existing_id: The ID of the file to retrieve.

            Returns:
                The file object if it exists, otherwise None.
            """
            try:
                entity_copy = RecordSet(
                    id=existing_id,
                    download_file=False,
                    version_number=self.version_number,
                    synapse_container_limit=self.synapse_container_limit,
                    parent_id=self.parent_id,
                )
                return await entity_copy.get_async(
                    synapse_client=synapse_client,
                    include_activity=self.activity is not None
                    or self.associate_activity_to_new_version,
                )
            except SynapseFileNotFoundError:
                return None

        if (
            self.create_or_update
            and not self._last_persistent_instance
            and (
                existing_entity_id := await get_id(
                    entity=self,
                    failure_strategy=None,
                    synapse_client=synapse_client,
                )
            )
            and (existing_file := await get_entity(existing_entity_id))
        ):
            return existing_file
        return None

    def _determine_fields_to_ignore_in_merge(self) -> List[str]:
        """
        Determine which fields should not be merged when merging two entities.

        This method returns a list of field names that should be ignored during
        entity merging operations. This allows for fine-tuned destructive updates
        of an entity based on the current configuration settings.

        The method has special handling for manifest uploads where specific fields
        are provided in the manifest and should take precedence over existing
        entity values.

        Returns:
            A list of field names that should not be merged from the existing entity.
        """
        fields_to_not_merge = []
        if not self.merge_existing_annotations:
            fields_to_not_merge.append("annotations")

        if not self.associate_activity_to_new_version:
            fields_to_not_merge.append("activity")

        return fields_to_not_merge

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        This method transforms the RecordSet object into a dictionary format that
        matches the structure expected by the Synapse REST API. It handles the
        conversion of Python snake_case attribute names to the camelCase format
        used by the API, and ensures that nested objects are properly serialized.

        Returns:
            A dictionary representation of this object formatted for API requests.
            None values are automatically removed from the dictionary.

        Example: Converting a RecordSet for API submission
            This method is used internally when storing or updating RecordSets:

            ```python
            from synapseclient.models import RecordSet

            record_set = RecordSet(
                name="My RecordSet",
                description="A test record set",
                parent_id="syn123456"
            )
            api_dict = record_set.to_synapse_request()
            # api_dict contains properly formatted data for the REST API
            ```
        """

        entity = {
            "concreteType": concrete_types.RECORD_SET_ENTITY,
            "name": self.name,
            "description": self.description,
            "id": self.id,
            "etag": self.etag,
            "createdOn": self.created_on,
            "modifiedOn": self.modified_on,
            "createdBy": self.created_by,
            "modifiedBy": self.modified_by,
            "parentId": self.parent_id,
            "versionNumber": self.version_number,
            "versionLabel": self.version_label,
            "versionComment": self.version_comment,
            "isLatestVersion": self.is_latest_version,
            "dataFileHandleId": self.data_file_handle_id,
            "upsertKey": self.upsert_keys,
            "csvDescriptor": (
                self.csv_descriptor.to_synapse_request()
                if self.csv_descriptor
                else None
            ),
            "validationSummary": (
                {
                    "containerId": self.validation_summary.container_id,
                    "totalNumberOfChildren": self.validation_summary.total_number_of_children,
                    "numberOfValidChildren": self.validation_summary.number_of_valid_children,
                    "numberOfInvalidChildren": self.validation_summary.number_of_invalid_children,
                    "numberOfUnknownChildren": self.validation_summary.number_of_unknown_children,
                    "generatedOn": self.validation_summary.generated_on,
                }
                if self.validation_summary
                else None
            ),
            "fileNameOverride": self.file_name_override,
        }
        delete_none_keys(entity)

        return entity

    async def store_async(
        self,
        parent: Optional[Union["Folder", "Project"]] = None,
        *,
        synapse_client: Optional[Synapse] = None,
    ) -> "RecordSet":
        """
        Store the RecordSet in Synapse.

        This method uploads or updates a RecordSet in Synapse. It can handle both
        creating new RecordSets and updating existing ones based on the
        `create_or_update` flag. The method supports file uploads, metadata updates,
        and merging with existing entities when appropriate.

        Arguments:
            parent: The parent Folder or Project for this RecordSet. If provided,
                this will override the `parent_id` attribute.
            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.

        Returns:
            The RecordSet object with updated metadata from Synapse after the
            store operation.

        Raises:
            ValueError: If the RecordSet does not have the required information
                for storing. Must have either: (ID with path or data_file_handle_id),
                or (path with parent_id), or (data_file_handle_id with parent_id).

        Example: Storing a new RecordSet
            Creating and storing a new RecordSet in Synapse:

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import RecordSet

            async def main():
                syn = Synapse()
                syn.login()

                record_set = RecordSet(
                    name="My RecordSet",
                    description="A dataset for analysis",
                    parent_id="syn123456",
                    path="/path/to/data.csv"
                )
                stored_record_set = await record_set.store_async()
                print(f"Stored RecordSet with ID: {stored_record_set.id}")

            asyncio.run(main())
            ```

            Updating an existing RecordSet:
            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import RecordSet

            async def main():
                syn = Synapse()
                syn.login()

                record_set = await RecordSet(id="syn789012").get_async()
                record_set.description = "Updated description"
                updated_record_set = await record_set.store_async()

            asyncio.run(main())
            ```
        """
        self.parent_id = parent.id if parent else self.parent_id
        if self._cannot_store():
            raise ValueError(
                "The file must have an (ID with a (path or `data_file_handle_id`)), or a "
                "(path with a (`parent_id` or parent with an id)), or a "
                "(data_file_handle_id with a (`parent_id` or parent with an id)) to store."
            )
        self.name = self.name or (guess_file_name(self.path) if self.path else None)
        client = Synapse.get_client(synapse_client=synapse_client)

        if existing_file := await self._find_existing_entity(synapse_client=client):
            merge_dataclass_entities(
                source=existing_file,
                destination=self,
                fields_to_ignore=self._determine_fields_to_ignore_in_merge(),
            )

        if self.id:
            trace.get_current_span().set_attributes(
                {
                    "synapse.id": self.id,
                }
            )

        if self.path:
            self.path = os.path.expanduser(self.path)
            async with client._get_parallel_file_transfer_semaphore(
                asyncio_event_loop=asyncio.get_running_loop()
            ):
                from synapseclient.models.file import _upload_file

                await _upload_file(entity_to_upload=self, synapse_client=client)
        elif self.data_file_handle_id:
            self.path = client.cache.get(file_handle_id=self.data_file_handle_id)

        if self.has_changed:
            entity = await store_entity(
                resource=self, entity=self.to_synapse_request(), synapse_client=client
            )

            self.fill_from_dict(entity=entity, set_annotations=False)

        re_read_required = await store_entity_components(
            root_resource=self, synapse_client=client
        )
        if re_read_required:
            before_download_file = self.download_file
            self.download_file = False
            await self.get_async(
                synapse_client=client,
            )
            self.download_file = before_download_file

        self._set_last_persistent_instance()

        client.logger.debug(f"Stored File {self.name}, id: {self.id}: {self.path}")
        # Clear the content_md5 so that it is recalculated if the file is updated
        self.content_md5 = None
        return self

    async def get_async(
        self,
        include_activity: bool = False,
        *,
        synapse_client: Optional[Synapse] = None,
    ) -> "RecordSet":
        """
        Get the RecordSet from Synapse.

        This method retrieves a RecordSet entity from Synapse. You may retrieve
        a RecordSet by either its ID or path. If you specify both, the ID will
        take precedence.

        If you specify the path and the RecordSet is stored in multiple locations
        in Synapse, only the first one found will be returned. The other matching
        RecordSets will be printed to the console.

        You may also specify a `version_number` to get a specific version of the
        RecordSet.

        Arguments:
            include_activity: If True, the activity will be included in the RecordSet
                if it exists.
            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.

        Returns:
            The RecordSet object with data populated from Synapse.

        Raises:
            ValueError: If the RecordSet does not have an ID or path to retrieve.

        Example: Retrieving a RecordSet by ID
            Get an existing RecordSet from Synapse:

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import RecordSet

            async def main():
                syn = Synapse()
                syn.login()

                record_set = await RecordSet(id="syn123").get_async()
                print(f"RecordSet name: {record_set.name}")

            asyncio.run(main())
            ```

            Downloading a RecordSet to a specific directory:
            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import RecordSet

            async def main():
                syn = Synapse()
                syn.login()

                record_set = await RecordSet(
                    id="syn123",
                    path="/path/to/download/directory"
                ).get_async()

            asyncio.run(main())
            ```

            Including activity information:
            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import RecordSet

            async def main():
                syn = Synapse()
                syn.login()

                record_set = await RecordSet(id="syn123").get_async(include_activity=True)
                if record_set.activity:
                    print(f"Activity: {record_set.activity.name}")

            asyncio.run(main())
            ```
        """
        if not self.id and not self.path:
            raise ValueError("The file must have an ID or path to get.")
        syn = Synapse.get_client(synapse_client=synapse_client)

        await self._load_local_md5()

        await get_from_entity_factory(
            entity_to_update=self,
            synapse_id_or_path=self.id or self.path,
            version=self.version_number,
            if_collision=self.if_collision,
            limit_search=self.synapse_container_limit or self.parent_id,
            download_file=self.download_file,
            download_location=(
                os.path.dirname(self.path)
                if self.path and os.path.isfile(self.path)
                else self.path
            ),
            md5=self.content_md5,
            synapse_client=syn,
        )

        if (
            self.data_file_handle_id
            and (not self.path or (self.path and not os.path.isfile(self.path)))
            and (cached_path := syn.cache.get(file_handle_id=self.data_file_handle_id))
        ):
            self.path = cached_path

        if include_activity:
            self.activity = await Activity.from_parent_async(
                parent=self, synapse_client=synapse_client
            )

        self._set_last_persistent_instance()
        Synapse.get_client(synapse_client=synapse_client).logger.debug(
            f"Got file {self.name}, id: {self.id}, path: {self.path}"
        )
        return self

    async def get_detailed_validation_results_async(
        self,
        download_location: Optional[str] = None,
        *,
        synapse_client: Optional[Synapse] = None,
    ) -> DATA_FRAME_TYPE:
        """
        Get detailed validation results for the RecordSet as a pandas DataFrame.

        This method downloads a CSV file containing detailed validation results for each row
        in the RecordSet. The validation results are generated when a RecordSet with a bound
        JSON schema is synchronized (pushed) from a Grid session. The CSV contains columns:
        - row_index: The index of the row in the RecordSet
        - is_valid: Boolean indicating if the row is valid according to the schema
        - validation_error_message: The primary validation error message (if any)
        - all_validation_messages: All validation messages for the row (if any)

        Arguments:
            download_location: Optional directory path where the validation results CSV
                should be downloaded. If not specified, the file will be downloaded to
                the Synapse cache directory. If the file is already cached, it will use
                the cached version unless a different download_location is specified.
            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.

        Returns:
            A pandas DataFrame containing the validation results, or None if no
            validation_file_handle_id is available (with a warning logged).

        Example: Get validation results for a RecordSet
            Get detailed validation results after synchronizing from a Grid session:

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import RecordSet, Grid
            from synapseclient.models.curation import SyncType

            async def main():
                syn = Synapse()
                syn.login()

                # Assuming you have a RecordSet with a bound schema
                record_set = await RecordSet(id="syn123").get_async()

                # Create and push a Grid session to generate validation results
                grid = await Grid(record_set_id=record_set.id).create_async()
                await grid.synchronize_async(sync_type=SyncType.PULL_PUSH)
                await grid.delete_async()

                # Re-fetch the RecordSet to get updated validation_file_handle_id
                record_set = await record_set.get_async()

                # Get the detailed validation results
                results_df = await record_set.get_detailed_validation_results_async()

                # Analyze the results
                print(f"Total rows: {len(results_df)}")
                print(f"Columns: {results_df.columns.tolist()}")

                # Filter for valid and invalid rows
                # Note: is_valid is boolean (True/False) for validated rows
                valid_rows = results_df[results_df['is_valid'] == True]  # noqa: E712
                invalid_rows = results_df[results_df['is_valid'] == False]  # noqa: E712

                print(f"Valid rows: {len(valid_rows)}")
                print(f"Invalid rows: {len(invalid_rows)}")

                # View invalid rows with their error messages
                if len(invalid_rows) > 0:
                    print(invalid_rows[['row_index', 'validation_error_message']])

            asyncio.run(main())
            ```
        """
        test_import_pandas()
        import pandas as pd

        client = Synapse.get_client(synapse_client=synapse_client)

        if not self.validation_file_handle_id:
            client.logger.warning(
                "No validation file handle ID found for this RecordSet. Cannot retrieve detailed validation results."
            )
            return None

        cached_file_path = client.cache.get(
            file_handle_id=self.validation_file_handle_id, path=download_location
        )

        # location in .synapseCache where the file would be corresponding to its FileHandleId
        synapse_cache_location = client.cache.get_cache_dir(
            file_handle_id=self.validation_file_handle_id
        )

        if download_location is not None:
            # Make sure the specified download location is a fully resolved directory
            download_location = ensure_download_location_is_directory(download_location)
        elif cached_file_path is not None:
            # file already cached so use that as the download location
            download_location = os.path.dirname(cached_file_path)
        else:
            # file not cached and no user-specified location so default to .synapseCache
            download_location = synapse_cache_location

        # Generate filename for the validation results CSV
        filename = f"SYNAPSE_RECORDSET_VALIDATION_{self.validation_file_handle_id}.csv"
        destination_path = os.path.join(download_location, filename)

        validation_file_path = await download_by_file_handle(
            file_handle_id=self.validation_file_handle_id,
            synapse_id=self.id,
            entity_type="FileEntity",
            destination=destination_path,
            synapse_client=client,
        )

        return pd.read_csv(validation_file_path)

    async def delete_async(
        self,
        version_only: Optional[bool] = False,
        *,
        synapse_client: Optional[Synapse] = None,
    ) -> None:
        """
        Delete the RecordSet from Synapse using its ID.

        This method removes a RecordSet entity from Synapse. You can choose to
        delete either a specific version or the entire RecordSet including all
        its versions.

        Arguments:
            version_only: If True, only the version specified in the `version_number`
                attribute of the RecordSet will be deleted. If False, the entire
                RecordSet including all versions will be deleted.
            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.

        Returns:
            None

        Raises:
            ValueError: If the RecordSet does not have an ID to delete.
            ValueError: If the RecordSet does not have a version number to delete a
                specific version, and `version_only` is True.

        Example: Deleting a RecordSet
            Delete an entire RecordSet and all its versions:

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import RecordSet

            async def main():
                syn = Synapse()
                syn.login()

                await RecordSet(id="syn123").delete_async()

            asyncio.run(main())
            ```

            Delete only a specific version:
            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import RecordSet

            async def main():
                syn = Synapse()
                syn.login()

                record_set = RecordSet(id="syn123", version_number=2)
                await record_set.delete_async(version_only=True)

            asyncio.run(main())
            ```
        """
        if not self.id:
            raise ValueError("The file must have an ID to delete.")
        if version_only and not self.version_number:
            raise ValueError("The file must have a version number to delete a version.")

        loop = asyncio.get_event_loop()
        await loop.run_in_executor(
            None,
            lambda: Synapse.get_client(synapse_client=synapse_client).delete(
                obj=self.id,
                version=self.version_number if version_only else None,
            ),
        )
        Synapse.get_client(synapse_client=synapse_client).logger.debug(
            f"Deleted file {self.id}"
        )

Methods:

get_async async

get_async(include_activity: bool = False, *, synapse_client: Optional[Synapse] = None) -> RecordSet

Get the RecordSet from Synapse.

This method retrieves a RecordSet entity from Synapse. You may retrieve a RecordSet by either its ID or path. If you specify both, the ID will take precedence.

If you specify the path and the RecordSet is stored in multiple locations in Synapse, only the first one found will be returned. The other matching RecordSets will be printed to the console.

You may also specify a version_number to get a specific version of the RecordSet.

PARAMETER DESCRIPTION
include_activity

If True, the activity will be included in the RecordSet if it exists.

TYPE: bool DEFAULT: False

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
RecordSet

The RecordSet object with data populated from Synapse.

RAISES DESCRIPTION
ValueError

If the RecordSet does not have an ID or path to retrieve.

Retrieving a RecordSet by ID

Get an existing RecordSet from Synapse:

import asyncio
from synapseclient import Synapse
from synapseclient.models import RecordSet

async def main():
    syn = Synapse()
    syn.login()

    record_set = await RecordSet(id="syn123").get_async()
    print(f"RecordSet name: {record_set.name}")

asyncio.run(main())

Downloading a RecordSet to a specific directory:

import asyncio
from synapseclient import Synapse
from synapseclient.models import RecordSet

async def main():
    syn = Synapse()
    syn.login()

    record_set = await RecordSet(
        id="syn123",
        path="/path/to/download/directory"
    ).get_async()

asyncio.run(main())

Including activity information:

import asyncio
from synapseclient import Synapse
from synapseclient.models import RecordSet

async def main():
    syn = Synapse()
    syn.login()

    record_set = await RecordSet(id="syn123").get_async(include_activity=True)
    if record_set.activity:
        print(f"Activity: {record_set.activity.name}")

asyncio.run(main())

Source code in synapseclient/models/recordset.py
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
async def get_async(
    self,
    include_activity: bool = False,
    *,
    synapse_client: Optional[Synapse] = None,
) -> "RecordSet":
    """
    Get the RecordSet from Synapse.

    This method retrieves a RecordSet entity from Synapse. You may retrieve
    a RecordSet by either its ID or path. If you specify both, the ID will
    take precedence.

    If you specify the path and the RecordSet is stored in multiple locations
    in Synapse, only the first one found will be returned. The other matching
    RecordSets will be printed to the console.

    You may also specify a `version_number` to get a specific version of the
    RecordSet.

    Arguments:
        include_activity: If True, the activity will be included in the RecordSet
            if it exists.
        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.

    Returns:
        The RecordSet object with data populated from Synapse.

    Raises:
        ValueError: If the RecordSet does not have an ID or path to retrieve.

    Example: Retrieving a RecordSet by ID
        Get an existing RecordSet from Synapse:

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import RecordSet

        async def main():
            syn = Synapse()
            syn.login()

            record_set = await RecordSet(id="syn123").get_async()
            print(f"RecordSet name: {record_set.name}")

        asyncio.run(main())
        ```

        Downloading a RecordSet to a specific directory:
        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import RecordSet

        async def main():
            syn = Synapse()
            syn.login()

            record_set = await RecordSet(
                id="syn123",
                path="/path/to/download/directory"
            ).get_async()

        asyncio.run(main())
        ```

        Including activity information:
        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import RecordSet

        async def main():
            syn = Synapse()
            syn.login()

            record_set = await RecordSet(id="syn123").get_async(include_activity=True)
            if record_set.activity:
                print(f"Activity: {record_set.activity.name}")

        asyncio.run(main())
        ```
    """
    if not self.id and not self.path:
        raise ValueError("The file must have an ID or path to get.")
    syn = Synapse.get_client(synapse_client=synapse_client)

    await self._load_local_md5()

    await get_from_entity_factory(
        entity_to_update=self,
        synapse_id_or_path=self.id or self.path,
        version=self.version_number,
        if_collision=self.if_collision,
        limit_search=self.synapse_container_limit or self.parent_id,
        download_file=self.download_file,
        download_location=(
            os.path.dirname(self.path)
            if self.path and os.path.isfile(self.path)
            else self.path
        ),
        md5=self.content_md5,
        synapse_client=syn,
    )

    if (
        self.data_file_handle_id
        and (not self.path or (self.path and not os.path.isfile(self.path)))
        and (cached_path := syn.cache.get(file_handle_id=self.data_file_handle_id))
    ):
        self.path = cached_path

    if include_activity:
        self.activity = await Activity.from_parent_async(
            parent=self, synapse_client=synapse_client
        )

    self._set_last_persistent_instance()
    Synapse.get_client(synapse_client=synapse_client).logger.debug(
        f"Got file {self.name}, id: {self.id}, path: {self.path}"
    )
    return self

store_async async

store_async(parent: Optional[Union[Folder, Project]] = None, *, synapse_client: Optional[Synapse] = None) -> RecordSet

Store the RecordSet in Synapse.

This method uploads or updates a RecordSet in Synapse. It can handle both creating new RecordSets and updating existing ones based on the create_or_update flag. The method supports file uploads, metadata updates, and merging with existing entities when appropriate.

PARAMETER DESCRIPTION
parent

The parent Folder or Project for this RecordSet. If provided, this will override the parent_id attribute.

TYPE: Optional[Union[Folder, Project]] DEFAULT: None

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
RecordSet

The RecordSet object with updated metadata from Synapse after the

RecordSet

store operation.

RAISES DESCRIPTION
ValueError

If the RecordSet does not have the required information for storing. Must have either: (ID with path or data_file_handle_id), or (path with parent_id), or (data_file_handle_id with parent_id).

Storing a new RecordSet

Creating and storing a new RecordSet in Synapse:

import asyncio
from synapseclient import Synapse
from synapseclient.models import RecordSet

async def main():
    syn = Synapse()
    syn.login()

    record_set = RecordSet(
        name="My RecordSet",
        description="A dataset for analysis",
        parent_id="syn123456",
        path="/path/to/data.csv"
    )
    stored_record_set = await record_set.store_async()
    print(f"Stored RecordSet with ID: {stored_record_set.id}")

asyncio.run(main())

Updating an existing RecordSet:

import asyncio
from synapseclient import Synapse
from synapseclient.models import RecordSet

async def main():
    syn = Synapse()
    syn.login()

    record_set = await RecordSet(id="syn789012").get_async()
    record_set.description = "Updated description"
    updated_record_set = await record_set.store_async()

asyncio.run(main())

Source code in synapseclient/models/recordset.py
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
async def store_async(
    self,
    parent: Optional[Union["Folder", "Project"]] = None,
    *,
    synapse_client: Optional[Synapse] = None,
) -> "RecordSet":
    """
    Store the RecordSet in Synapse.

    This method uploads or updates a RecordSet in Synapse. It can handle both
    creating new RecordSets and updating existing ones based on the
    `create_or_update` flag. The method supports file uploads, metadata updates,
    and merging with existing entities when appropriate.

    Arguments:
        parent: The parent Folder or Project for this RecordSet. If provided,
            this will override the `parent_id` attribute.
        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.

    Returns:
        The RecordSet object with updated metadata from Synapse after the
        store operation.

    Raises:
        ValueError: If the RecordSet does not have the required information
            for storing. Must have either: (ID with path or data_file_handle_id),
            or (path with parent_id), or (data_file_handle_id with parent_id).

    Example: Storing a new RecordSet
        Creating and storing a new RecordSet in Synapse:

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import RecordSet

        async def main():
            syn = Synapse()
            syn.login()

            record_set = RecordSet(
                name="My RecordSet",
                description="A dataset for analysis",
                parent_id="syn123456",
                path="/path/to/data.csv"
            )
            stored_record_set = await record_set.store_async()
            print(f"Stored RecordSet with ID: {stored_record_set.id}")

        asyncio.run(main())
        ```

        Updating an existing RecordSet:
        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import RecordSet

        async def main():
            syn = Synapse()
            syn.login()

            record_set = await RecordSet(id="syn789012").get_async()
            record_set.description = "Updated description"
            updated_record_set = await record_set.store_async()

        asyncio.run(main())
        ```
    """
    self.parent_id = parent.id if parent else self.parent_id
    if self._cannot_store():
        raise ValueError(
            "The file must have an (ID with a (path or `data_file_handle_id`)), or a "
            "(path with a (`parent_id` or parent with an id)), or a "
            "(data_file_handle_id with a (`parent_id` or parent with an id)) to store."
        )
    self.name = self.name or (guess_file_name(self.path) if self.path else None)
    client = Synapse.get_client(synapse_client=synapse_client)

    if existing_file := await self._find_existing_entity(synapse_client=client):
        merge_dataclass_entities(
            source=existing_file,
            destination=self,
            fields_to_ignore=self._determine_fields_to_ignore_in_merge(),
        )

    if self.id:
        trace.get_current_span().set_attributes(
            {
                "synapse.id": self.id,
            }
        )

    if self.path:
        self.path = os.path.expanduser(self.path)
        async with client._get_parallel_file_transfer_semaphore(
            asyncio_event_loop=asyncio.get_running_loop()
        ):
            from synapseclient.models.file import _upload_file

            await _upload_file(entity_to_upload=self, synapse_client=client)
    elif self.data_file_handle_id:
        self.path = client.cache.get(file_handle_id=self.data_file_handle_id)

    if self.has_changed:
        entity = await store_entity(
            resource=self, entity=self.to_synapse_request(), synapse_client=client
        )

        self.fill_from_dict(entity=entity, set_annotations=False)

    re_read_required = await store_entity_components(
        root_resource=self, synapse_client=client
    )
    if re_read_required:
        before_download_file = self.download_file
        self.download_file = False
        await self.get_async(
            synapse_client=client,
        )
        self.download_file = before_download_file

    self._set_last_persistent_instance()

    client.logger.debug(f"Stored File {self.name}, id: {self.id}: {self.path}")
    # Clear the content_md5 so that it is recalculated if the file is updated
    self.content_md5 = None
    return self

delete_async async

delete_async(version_only: Optional[bool] = False, *, synapse_client: Optional[Synapse] = None) -> None

Delete the RecordSet from Synapse using its ID.

This method removes a RecordSet entity from Synapse. You can choose to delete either a specific version or the entire RecordSet including all its versions.

PARAMETER DESCRIPTION
version_only

If True, only the version specified in the version_number attribute of the RecordSet will be deleted. If False, the entire RecordSet including all versions will be deleted.

TYPE: Optional[bool] DEFAULT: False

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
None

None

RAISES DESCRIPTION
ValueError

If the RecordSet does not have an ID to delete.

ValueError

If the RecordSet does not have a version number to delete a specific version, and version_only is True.

Deleting a RecordSet

Delete an entire RecordSet and all its versions:

import asyncio
from synapseclient import Synapse
from synapseclient.models import RecordSet

async def main():
    syn = Synapse()
    syn.login()

    await RecordSet(id="syn123").delete_async()

asyncio.run(main())

Delete only a specific version:

import asyncio
from synapseclient import Synapse
from synapseclient.models import RecordSet

async def main():
    syn = Synapse()
    syn.login()

    record_set = RecordSet(id="syn123", version_number=2)
    await record_set.delete_async(version_only=True)

asyncio.run(main())

Source code in synapseclient/models/recordset.py
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
async def delete_async(
    self,
    version_only: Optional[bool] = False,
    *,
    synapse_client: Optional[Synapse] = None,
) -> None:
    """
    Delete the RecordSet from Synapse using its ID.

    This method removes a RecordSet entity from Synapse. You can choose to
    delete either a specific version or the entire RecordSet including all
    its versions.

    Arguments:
        version_only: If True, only the version specified in the `version_number`
            attribute of the RecordSet will be deleted. If False, the entire
            RecordSet including all versions will be deleted.
        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.

    Returns:
        None

    Raises:
        ValueError: If the RecordSet does not have an ID to delete.
        ValueError: If the RecordSet does not have a version number to delete a
            specific version, and `version_only` is True.

    Example: Deleting a RecordSet
        Delete an entire RecordSet and all its versions:

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import RecordSet

        async def main():
            syn = Synapse()
            syn.login()

            await RecordSet(id="syn123").delete_async()

        asyncio.run(main())
        ```

        Delete only a specific version:
        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import RecordSet

        async def main():
            syn = Synapse()
            syn.login()

            record_set = RecordSet(id="syn123", version_number=2)
            await record_set.delete_async(version_only=True)

        asyncio.run(main())
        ```
    """
    if not self.id:
        raise ValueError("The file must have an ID to delete.")
    if version_only and not self.version_number:
        raise ValueError("The file must have a version number to delete a version.")

    loop = asyncio.get_event_loop()
    await loop.run_in_executor(
        None,
        lambda: Synapse.get_client(synapse_client=synapse_client).delete(
            obj=self.id,
            version=self.version_number if version_only else None,
        ),
    )
    Synapse.get_client(synapse_client=synapse_client).logger.debug(
        f"Deleted file {self.id}"
    )

get_detailed_validation_results_async async

get_detailed_validation_results_async(download_location: Optional[str] = None, *, synapse_client: Optional[Synapse] = None) -> DataFrame

Get detailed validation results for the RecordSet as a pandas DataFrame.

This method downloads a CSV file containing detailed validation results for each row in the RecordSet. The validation results are generated when a RecordSet with a bound JSON schema is synchronized (pushed) from a Grid session. The CSV contains columns: - row_index: The index of the row in the RecordSet - is_valid: Boolean indicating if the row is valid according to the schema - validation_error_message: The primary validation error message (if any) - all_validation_messages: All validation messages for the row (if any)

PARAMETER DESCRIPTION
download_location

Optional directory path where the validation results CSV should be downloaded. If not specified, the file will be downloaded to the Synapse cache directory. If the file is already cached, it will use the cached version unless a different download_location is specified.

TYPE: Optional[str] DEFAULT: None

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
DataFrame

A pandas DataFrame containing the validation results, or None if no

DataFrame

validation_file_handle_id is available (with a warning logged).

Get validation results for a RecordSet

Get detailed validation results after synchronizing from a Grid session:

import asyncio
from synapseclient import Synapse
from synapseclient.models import RecordSet, Grid
from synapseclient.models.curation import SyncType

async def main():
    syn = Synapse()
    syn.login()

    # Assuming you have a RecordSet with a bound schema
    record_set = await RecordSet(id="syn123").get_async()

    # Create and push a Grid session to generate validation results
    grid = await Grid(record_set_id=record_set.id).create_async()
    await grid.synchronize_async(sync_type=SyncType.PULL_PUSH)
    await grid.delete_async()

    # Re-fetch the RecordSet to get updated validation_file_handle_id
    record_set = await record_set.get_async()

    # Get the detailed validation results
    results_df = await record_set.get_detailed_validation_results_async()

    # Analyze the results
    print(f"Total rows: {len(results_df)}")
    print(f"Columns: {results_df.columns.tolist()}")

    # Filter for valid and invalid rows
    # Note: is_valid is boolean (True/False) for validated rows
    valid_rows = results_df[results_df['is_valid'] == True]  # noqa: E712
    invalid_rows = results_df[results_df['is_valid'] == False]  # noqa: E712

    print(f"Valid rows: {len(valid_rows)}")
    print(f"Invalid rows: {len(invalid_rows)}")

    # View invalid rows with their error messages
    if len(invalid_rows) > 0:
        print(invalid_rows[['row_index', 'validation_error_message']])

asyncio.run(main())
Source code in synapseclient/models/recordset.py
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
async def get_detailed_validation_results_async(
    self,
    download_location: Optional[str] = None,
    *,
    synapse_client: Optional[Synapse] = None,
) -> DATA_FRAME_TYPE:
    """
    Get detailed validation results for the RecordSet as a pandas DataFrame.

    This method downloads a CSV file containing detailed validation results for each row
    in the RecordSet. The validation results are generated when a RecordSet with a bound
    JSON schema is synchronized (pushed) from a Grid session. The CSV contains columns:
    - row_index: The index of the row in the RecordSet
    - is_valid: Boolean indicating if the row is valid according to the schema
    - validation_error_message: The primary validation error message (if any)
    - all_validation_messages: All validation messages for the row (if any)

    Arguments:
        download_location: Optional directory path where the validation results CSV
            should be downloaded. If not specified, the file will be downloaded to
            the Synapse cache directory. If the file is already cached, it will use
            the cached version unless a different download_location is specified.
        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.

    Returns:
        A pandas DataFrame containing the validation results, or None if no
        validation_file_handle_id is available (with a warning logged).

    Example: Get validation results for a RecordSet
        Get detailed validation results after synchronizing from a Grid session:

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import RecordSet, Grid
        from synapseclient.models.curation import SyncType

        async def main():
            syn = Synapse()
            syn.login()

            # Assuming you have a RecordSet with a bound schema
            record_set = await RecordSet(id="syn123").get_async()

            # Create and push a Grid session to generate validation results
            grid = await Grid(record_set_id=record_set.id).create_async()
            await grid.synchronize_async(sync_type=SyncType.PULL_PUSH)
            await grid.delete_async()

            # Re-fetch the RecordSet to get updated validation_file_handle_id
            record_set = await record_set.get_async()

            # Get the detailed validation results
            results_df = await record_set.get_detailed_validation_results_async()

            # Analyze the results
            print(f"Total rows: {len(results_df)}")
            print(f"Columns: {results_df.columns.tolist()}")

            # Filter for valid and invalid rows
            # Note: is_valid is boolean (True/False) for validated rows
            valid_rows = results_df[results_df['is_valid'] == True]  # noqa: E712
            invalid_rows = results_df[results_df['is_valid'] == False]  # noqa: E712

            print(f"Valid rows: {len(valid_rows)}")
            print(f"Invalid rows: {len(invalid_rows)}")

            # View invalid rows with their error messages
            if len(invalid_rows) > 0:
                print(invalid_rows[['row_index', 'validation_error_message']])

        asyncio.run(main())
        ```
    """
    test_import_pandas()
    import pandas as pd

    client = Synapse.get_client(synapse_client=synapse_client)

    if not self.validation_file_handle_id:
        client.logger.warning(
            "No validation file handle ID found for this RecordSet. Cannot retrieve detailed validation results."
        )
        return None

    cached_file_path = client.cache.get(
        file_handle_id=self.validation_file_handle_id, path=download_location
    )

    # location in .synapseCache where the file would be corresponding to its FileHandleId
    synapse_cache_location = client.cache.get_cache_dir(
        file_handle_id=self.validation_file_handle_id
    )

    if download_location is not None:
        # Make sure the specified download location is a fully resolved directory
        download_location = ensure_download_location_is_directory(download_location)
    elif cached_file_path is not None:
        # file already cached so use that as the download location
        download_location = os.path.dirname(cached_file_path)
    else:
        # file not cached and no user-specified location so default to .synapseCache
        download_location = synapse_cache_location

    # Generate filename for the validation results CSV
    filename = f"SYNAPSE_RECORDSET_VALIDATION_{self.validation_file_handle_id}.csv"
    destination_path = os.path.join(download_location, filename)

    validation_file_path = await download_by_file_handle(
        file_handle_id=self.validation_file_handle_id,
        synapse_id=self.id,
        entity_type="FileEntity",
        destination=destination_path,
        synapse_client=client,
    )

    return pd.read_csv(validation_file_path)

get_acl_async async

get_acl_async(principal_id: int = None, check_benefactor: bool = True, *, synapse_client: Optional[Synapse] = None) -> List[str]

Get the ACL that a user or group has on an Entity.

Note: If the entity does not have local sharing settings, or ACL set directly on it, this will look up the ACL on the benefactor of the entity. The benefactor is the entity that the current entity inherits its permissions from. The benefactor is usually the parent entity, but it can be any ancestor in the hierarchy. For example, a newly created Project will be its own benefactor, while a new FileEntity's benefactor will start off as its containing Project or Folder. If the entity already has local sharing settings, the benefactor would be itself.

PARAMETER DESCRIPTION
principal_id

Identifier of a user or group (defaults to PUBLIC users)

TYPE: int DEFAULT: None

check_benefactor

If True (default), check the benefactor for the entity to get the ACL. If False, only check the entity itself. This is useful for checking the ACL of an entity that has local sharing settings, but you want to check the ACL of the entity itself and not the benefactor it may inherit from.

TYPE: bool DEFAULT: True

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
List[str]

An array containing some combination of ['READ', 'UPDATE', 'CREATE', 'DELETE', 'DOWNLOAD', 'MODERATE', 'CHANGE_PERMISSIONS', 'CHANGE_SETTINGS'] or an empty array

Source code in synapseclient/models/mixins/access_control.py
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
async def get_acl_async(
    self,
    principal_id: int = None,
    check_benefactor: bool = True,
    *,
    synapse_client: Optional[Synapse] = None,
) -> List[str]:
    """
    Get the [ACL][synapseclient.core.models.permission.Permissions.access_types]
    that a user or group has on an Entity.

    Note: If the entity does not have local sharing settings, or ACL set directly
    on it, this will look up the ACL on the benefactor of the entity. The
    benefactor is the entity that the current entity inherits its permissions from.
    The benefactor is usually the parent entity, but it can be any ancestor in the
    hierarchy. For example, a newly created Project will be its own benefactor,
    while a new FileEntity's benefactor will start off as its containing Project or
    Folder. If the entity already has local sharing settings, the benefactor would
    be itself.

    Arguments:
        principal_id: Identifier of a user or group (defaults to PUBLIC users)
        check_benefactor: If True (default), check the benefactor for the entity
            to get the ACL. If False, only check the entity itself.
            This is useful for checking the ACL of an entity that has local sharing
            settings, but you want to check the ACL of the entity itself and not
            the benefactor it may inherit from.
        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.

    Returns:
        An array containing some combination of
            ['READ', 'UPDATE', 'CREATE', 'DELETE', 'DOWNLOAD', 'MODERATE',
            'CHANGE_PERMISSIONS', 'CHANGE_SETTINGS']
            or an empty array
    """
    return await get_entity_acl_list(
        entity_id=self.id,
        principal_id=str(principal_id) if principal_id is not None else None,
        check_benefactor=check_benefactor,
        synapse_client=synapse_client,
    )

get_permissions_async async

get_permissions_async(*, synapse_client: Optional[Synapse] = None) -> Permissions

Get the permissions that the caller has on an Entity.

PARAMETER DESCRIPTION
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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
Permissions

A Permissions object

Using this function:

Getting permissions for a Synapse Entity

import asyncio
from synapseclient import Synapse
from synapseclient.models import File

syn = Synapse()
syn.login()

async def main():
    permissions = await File(id="syn123").get_permissions_async()

asyncio.run(main())

Getting access types list from the Permissions object

permissions.access_types
Source code in synapseclient/models/mixins/access_control.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
async def get_permissions_async(
    self,
    *,
    synapse_client: Optional[Synapse] = None,
) -> "Permissions":
    """
    Get the [permissions][synapseclient.core.models.permission.Permissions]
    that the caller has on an Entity.

    Arguments:
        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.

    Returns:
        A Permissions object


    Example: Using this function:
        Getting permissions for a Synapse Entity

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import File

        syn = Synapse()
        syn.login()

        async def main():
            permissions = await File(id="syn123").get_permissions_async()

        asyncio.run(main())
        ```

        Getting access types list from the Permissions object

        ```
        permissions.access_types
        ```
    """
    from synapseclient.core.models.permission import Permissions

    permissions_dict = await get_entity_permissions(
        entity_id=self.id,
        synapse_client=synapse_client,
    )
    return Permissions.from_dict(data=permissions_dict)

set_permissions_async async

set_permissions_async(principal_id: int = None, access_type: List[str] = None, modify_benefactor: bool = False, warn_if_inherits: bool = True, overwrite: bool = True, *, synapse_client: Optional[Synapse] = None) -> Dict[str, Union[str, list]]

Sets permission that a user or group has on an Entity. An Entity may have its own ACL or inherit its ACL from a benefactor.

PARAMETER DESCRIPTION
principal_id

Identifier of a user or group. 273948 is for all registered Synapse users and 273949 is for public access. None implies public access.

TYPE: int DEFAULT: None

access_type

Type of permission to be granted. One or more of CREATE, READ, DOWNLOAD, UPDATE, DELETE, CHANGE_PERMISSIONS.

Defaults to ['READ', 'DOWNLOAD']

TYPE: List[str] DEFAULT: None

modify_benefactor

Set as True when modifying a benefactor's ACL. The term 'benefactor' is used to indicate which Entity an Entity inherits its ACL from. For example, a newly created Project will be its own benefactor, while a new FileEntity's benefactor will start off as its containing Project. If the entity already has local sharing settings the benefactor would be itself. It may also be the immediate parent, somewhere in the parent tree, or the project itself.

TYPE: bool DEFAULT: False

warn_if_inherits

When modify_benefactor is True, this does not have any effect. When modify_benefactor is False, and warn_if_inherits is True, a warning log message is produced if the benefactor for the entity you passed into the function is not itself, i.e., it's the parent folder, or another entity in the parent tree.

TYPE: bool DEFAULT: True

overwrite

By default this function overwrites existing permissions for the specified user. Set this flag to False to add new permissions non-destructively.

TYPE: bool DEFAULT: True

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
Dict[str, Union[str, list]]
Setting permissions

Grant all registered users download access

import asyncio
from synapseclient import Synapse
from synapseclient.models import File

syn = Synapse()
syn.login()

async def main():
    await File(id="syn123").set_permissions_async(principal_id=273948, access_type=['READ','DOWNLOAD'])

asyncio.run(main())

Grant the public view access

import asyncio
from synapseclient import Synapse
from synapseclient.models import File

syn = Synapse()
syn.login()

async def main():
    await File(id="syn123").set_permissions_async(principal_id=273949, access_type=['READ'])

asyncio.run(main())
Source code in synapseclient/models/mixins/access_control.py
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
async def set_permissions_async(
    self,
    principal_id: int = None,
    access_type: List[str] = None,
    modify_benefactor: bool = False,
    warn_if_inherits: bool = True,
    overwrite: bool = True,
    *,
    synapse_client: Optional[Synapse] = None,
) -> Dict[str, Union[str, list]]:
    """
    Sets permission that a user or group has on an Entity.
    An Entity may have its own ACL or inherit its ACL from a benefactor.

    Arguments:
        principal_id: Identifier of a user or group. `273948` is for all
            registered Synapse users and `273949` is for public access.
            None implies public access.
        access_type: Type of permission to be granted. One or more of CREATE,
            READ, DOWNLOAD, UPDATE, DELETE, CHANGE_PERMISSIONS.

            **Defaults to ['READ', 'DOWNLOAD']**
        modify_benefactor: Set as True when modifying a benefactor's ACL. The term
            'benefactor' is used to indicate which Entity an Entity inherits its
            ACL from. For example, a newly created Project will be its own
            benefactor, while a new FileEntity's benefactor will start off as its
            containing Project. If the entity already has local sharing settings
            the benefactor would be itself. It may also be the immediate parent,
            somewhere in the parent tree, or the project itself.
        warn_if_inherits: When `modify_benefactor` is True, this does not have any
            effect. When `modify_benefactor` is False, and `warn_if_inherits` is
            True, a warning log message is produced if the benefactor for the
            entity you passed into the function is not itself, i.e., it's the
            parent folder, or another entity in the parent tree.
        overwrite: By default this function overwrites existing permissions for
            the specified user. Set this flag to False to add new permissions
            non-destructively.
        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.

    Returns:
        An Access Control List object matching <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/AccessControlList.html>.

    Example: Setting permissions
        Grant all registered users download access

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import File

        syn = Synapse()
        syn.login()

        async def main():
            await File(id="syn123").set_permissions_async(principal_id=273948, access_type=['READ','DOWNLOAD'])

        asyncio.run(main())
        ```

        Grant the public view access

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import File

        syn = Synapse()
        syn.login()

        async def main():
            await File(id="syn123").set_permissions_async(principal_id=273949, access_type=['READ'])

        asyncio.run(main())
        ```
    """
    if access_type is None:
        access_type = ["READ", "DOWNLOAD"]

    return await set_entity_permissions(
        entity_id=self.id,
        principal_id=str(principal_id) if principal_id is not None else None,
        access_type=access_type,
        modify_benefactor=modify_benefactor,
        warn_if_inherits=warn_if_inherits,
        overwrite=overwrite,
        synapse_client=synapse_client,
    )

delete_permissions_async async

delete_permissions_async(include_self: bool = True, include_container_content: bool = False, recursive: bool = False, target_entity_types: Optional[List[str]] = None, dry_run: bool = False, show_acl_details: bool = True, show_files_in_containers: bool = True, *, synapse_client: Optional[Synapse] = None, _benefactor_tracker: Optional[BenefactorTracker] = None) -> None

Delete the entire Access Control List (ACL) for a given Entity. This is not scoped to a specific user or group, but rather removes all permissions associated with the Entity. After this operation, the Entity will inherit permissions from its benefactor, which is typically its parent entity or the Project it belongs to.

In order to remove permissions for a specific user or group, you should use the set_permissions_async method with the access_type set to an empty list.

By default, Entities such as FileEntity and Folder inherit their permission from their containing Project. For such Entities the Project is the Entity's 'benefactor'. This permission inheritance can be overridden by creating an ACL for the Entity. When this occurs the Entity becomes its own benefactor and all permission are determined by its own ACL.

If the ACL of an Entity is deleted, then its benefactor will automatically be set to its parent's benefactor.

Special notice for Projects: The ACL for a Project cannot be deleted, you must individually update or revoke the permissions for each user or group.

PARAMETER DESCRIPTION
include_self

If True (default), delete the ACL of the current entity. If False, skip deleting the ACL of the current entity.

TYPE: bool DEFAULT: True

include_container_content

If True, delete ACLs from contents directly within containers (files and folders inside self). This must be set to True for recursive to have any effect. Defaults to False.

TYPE: bool DEFAULT: False

recursive

If True and the entity is a container (e.g., Project or Folder), recursively process child containers. Note that this must be used with include_container_content=True to have any effect. Setting recursive=True with include_container_content=False will raise a ValueError. Only works on classes that support the sync_from_synapse_async method.

TYPE: bool DEFAULT: False

target_entity_types

Specify which entity types to process when deleting ACLs. Allowed values are "folder", "file", "project", "table", "entityview", "materializedview", "virtualtable", "dataset", "datasetcollection", "submissionview" (case-insensitive). If None, defaults to ["folder", "file"]. This does not affect the entity type of the current entity, which is always processed if include_self=True.

TYPE: Optional[List[str]] DEFAULT: None

dry_run

If True, log the changes that would be made instead of actually performing the deletions. When enabled, all ACL deletion operations are simulated and logged at info level. Defaults to False.

TYPE: bool DEFAULT: False

show_acl_details

When dry_run=True, controls whether current ACL details are displayed for entities that will have their permissions changed. If True (default), shows detailed ACL information. If False, hides ACL details for cleaner output. Has no effect when dry_run=False.

TYPE: bool DEFAULT: True

show_files_in_containers

When dry_run=True, controls whether files within containers are displayed in the preview. If True (default), shows all files. If False, hides files when their only change is benefactor inheritance (but still shows files with local ACLs being deleted). Has no effect when dry_run=False.

TYPE: bool DEFAULT: True

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.

TYPE: Optional[Synapse] DEFAULT: None

_benefactor_tracker

Internal use tracker for managing benefactor relationships. Used for recursive functionality to track which entities will be affected

TYPE: Optional[BenefactorTracker] DEFAULT: None

RETURNS DESCRIPTION
None

None

RAISES DESCRIPTION
ValueError

If the entity does not have an ID or if an invalid entity type is provided.

SynapseHTTPError

If there are permission issues or if the entity already inherits permissions.

Exception

For any other errors that may occur during the process.

Note: The caller must be granted ACCESS_TYPE.CHANGE_PERMISSIONS on the Entity to call this method.

Delete permissions for a single entity
import asyncio
from synapseclient import Synapse
from synapseclient.models import File

syn = Synapse()
syn.login()

async def main():
    await File(id="syn123").delete_permissions_async()

asyncio.run(main())
Delete permissions recursively for a folder and all its children
import asyncio
from synapseclient import Synapse
from synapseclient.models import Folder

syn = Synapse()
syn.login()

async def main():
    # Delete permissions for this folder only (does not affect children)
    await Folder(id="syn123").delete_permissions_async()

    # Delete permissions for all files and folders directly within this folder,
    # but not the folder itself
    await Folder(id="syn123").delete_permissions_async(
        include_self=False,
        include_container_content=True
    )

    # Delete permissions for all items in the entire hierarchy (folders and their files)
    # Both recursive and include_container_content must be True
    await Folder(id="syn123").delete_permissions_async(
        recursive=True,
        include_container_content=True
    )

    # Delete permissions only for folder entities within this folder recursively
    # and their contents
    await Folder(id="syn123").delete_permissions_async(
        recursive=True,
        include_container_content=True,
        target_entity_types=["folder"]
    )

    # Delete permissions only for files within this folder and all subfolders
    await Folder(id="syn123").delete_permissions_async(
        include_self=False,
        recursive=True,
        include_container_content=True,
        target_entity_types=["file"]
    )

    # Delete permissions for specific entity types (e.g., tables and views)
    await Folder(id="syn123").delete_permissions_async(
        recursive=True,
        include_container_content=True,
        target_entity_types=["table", "entityview", "materializedview"]
    )

    # Dry run example: Log what would be deleted without making changes
    await Folder(id="syn123").delete_permissions_async(
        recursive=True,
        include_container_content=True,
        dry_run=True
    )
asyncio.run(main())
Source code in synapseclient/models/mixins/access_control.py
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
async def delete_permissions_async(
    self,
    include_self: bool = True,
    include_container_content: bool = False,
    recursive: bool = False,
    target_entity_types: Optional[List[str]] = None,
    dry_run: bool = False,
    show_acl_details: bool = True,
    show_files_in_containers: bool = True,
    *,
    synapse_client: Optional[Synapse] = None,
    _benefactor_tracker: Optional[BenefactorTracker] = None,
) -> None:
    """
    Delete the entire Access Control List (ACL) for a given Entity. This is not
    scoped to a specific user or group, but rather removes all permissions
    associated with the Entity. After this operation, the Entity will inherit
    permissions from its benefactor, which is typically its parent entity or
    the Project it belongs to.

    In order to remove permissions for a specific user or group, you
    should use the `set_permissions_async` method with the `access_type` set to
    an empty list.

    By default, Entities such as FileEntity and Folder inherit their permission from
    their containing Project. For such Entities the Project is the Entity's 'benefactor'.
    This permission inheritance can be overridden by creating an ACL for the Entity.
    When this occurs the Entity becomes its own benefactor and all permission are
    determined by its own ACL.

    If the ACL of an Entity is deleted, then its benefactor will automatically be set
    to its parent's benefactor.

    **Special notice for Projects:** The ACL for a Project cannot be deleted, you
    must individually update or revoke the permissions for each user or group.

    Arguments:
        include_self: If True (default), delete the ACL of the current entity.
            If False, skip deleting the ACL of the current entity.
        include_container_content: If True, delete ACLs from contents directly within
            containers (files and folders inside self). This must be set to
            True for recursive to have any effect. Defaults to False.
        recursive: If True and the entity is a container (e.g., Project or Folder),
            recursively process child containers. Note that this must be used with
            include_container_content=True to have any effect. Setting recursive=True
            with include_container_content=False will raise a ValueError.
            Only works on classes that support the `sync_from_synapse_async` method.
        target_entity_types: Specify which entity types to process when deleting ACLs.
            Allowed values are "folder", "file", "project", "table", "entityview",
            "materializedview", "virtualtable", "dataset", "datasetcollection",
            "submissionview" (case-insensitive). If None, defaults to ["folder", "file"].
            This does not affect the entity type of the current entity, which is always
            processed if `include_self=True`.
        dry_run: If True, log the changes that would be made instead of actually
            performing the deletions. When enabled, all ACL deletion operations are
            simulated and logged at info level. Defaults to False.
        show_acl_details: When dry_run=True, controls whether current ACL details are
            displayed for entities that will have their permissions changed. If True (default),
            shows detailed ACL information. If False, hides ACL details for cleaner output.
            Has no effect when dry_run=False.
        show_files_in_containers: When dry_run=True, controls whether files within containers
            are displayed in the preview. If True (default), shows all files. If False, hides
            files when their only change is benefactor inheritance (but still shows files with
            local ACLs being deleted). Has no effect when dry_run=False.
        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.
        _benefactor_tracker: Internal use tracker for managing benefactor relationships.
            Used for recursive functionality to track which entities will be affected

    Returns:
        None

    Raises:
        ValueError: If the entity does not have an ID or if an invalid entity type is provided.
        SynapseHTTPError: If there are permission issues or if the entity already inherits permissions.
        Exception: For any other errors that may occur during the process.

    Note: The caller must be granted ACCESS_TYPE.CHANGE_PERMISSIONS on the Entity to
    call this method.

    Example: Delete permissions for a single entity
        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import File

        syn = Synapse()
        syn.login()

        async def main():
            await File(id="syn123").delete_permissions_async()

        asyncio.run(main())
        ```

    Example: Delete permissions recursively for a folder and all its children
        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Folder

        syn = Synapse()
        syn.login()

        async def main():
            # Delete permissions for this folder only (does not affect children)
            await Folder(id="syn123").delete_permissions_async()

            # Delete permissions for all files and folders directly within this folder,
            # but not the folder itself
            await Folder(id="syn123").delete_permissions_async(
                include_self=False,
                include_container_content=True
            )

            # Delete permissions for all items in the entire hierarchy (folders and their files)
            # Both recursive and include_container_content must be True
            await Folder(id="syn123").delete_permissions_async(
                recursive=True,
                include_container_content=True
            )

            # Delete permissions only for folder entities within this folder recursively
            # and their contents
            await Folder(id="syn123").delete_permissions_async(
                recursive=True,
                include_container_content=True,
                target_entity_types=["folder"]
            )

            # Delete permissions only for files within this folder and all subfolders
            await Folder(id="syn123").delete_permissions_async(
                include_self=False,
                recursive=True,
                include_container_content=True,
                target_entity_types=["file"]
            )

            # Delete permissions for specific entity types (e.g., tables and views)
            await Folder(id="syn123").delete_permissions_async(
                recursive=True,
                include_container_content=True,
                target_entity_types=["table", "entityview", "materializedview"]
            )

            # Dry run example: Log what would be deleted without making changes
            await Folder(id="syn123").delete_permissions_async(
                recursive=True,
                include_container_content=True,
                dry_run=True
            )
        asyncio.run(main())
        ```
    """
    if not self.id:
        raise ValueError("The entity must have an ID to delete permissions.")

    client = Synapse.get_client(synapse_client=synapse_client)

    if include_self and self.__class__.__name__.lower() == "project":
        client.logger.warning(
            "The ACL for a Project cannot be deleted, you must individually update or "
            "revoke the permissions for each user or group. Continuing without deleting "
            "the Project's ACL."
        )
        include_self = False

    normalized_types = self._normalize_target_entity_types(target_entity_types)

    is_top_level = not _benefactor_tracker
    benefactor_tracker = _benefactor_tracker or BenefactorTracker()

    should_process_children = (recursive or include_container_content) and hasattr(
        self, "sync_from_synapse_async"
    )
    all_entities = [self] if include_self else []

    custom_message = "Deleting ACLs [Dry Run]..." if dry_run else "Deleting ACLs..."
    with shared_download_progress_bar(
        file_size=1, synapse_client=client, custom_message=custom_message, unit=None
    ) as progress_bar:
        if progress_bar:
            progress_bar.update(1)  # Initial setup complete

        if should_process_children:
            if recursive and not include_container_content:
                raise ValueError(
                    "When recursive=True, include_container_content must also be True. "
                    "Setting recursive=True with include_container_content=False has no effect."
                )

            if progress_bar:
                progress_bar.total += 1
                progress_bar.refresh()

            all_entities = await self._collect_entities(
                client=client,
                target_entity_types=normalized_types,
                include_container_content=include_container_content,
                recursive=recursive,
                progress_bar=progress_bar,
            )
            if progress_bar:
                progress_bar.update(1)

            entity_ids = [entity.id for entity in all_entities if entity.id]
            if entity_ids:
                if progress_bar:
                    progress_bar.total += 1
                    progress_bar.refresh()
                await benefactor_tracker.track_entity_benefactor(
                    entity_ids=entity_ids,
                    synapse_client=client,
                    progress_bar=progress_bar,
                )
            else:
                if progress_bar:
                    progress_bar.total += 1
                    progress_bar.refresh()
                    progress_bar.update(1)

        if is_top_level:
            if progress_bar:
                progress_bar.total += 1
                progress_bar.refresh()
            await self._build_and_log_run_tree(
                client=client,
                benefactor_tracker=benefactor_tracker,
                collected_entities=all_entities,
                include_self=include_self,
                show_acl_details=show_acl_details,
                show_files_in_containers=show_files_in_containers,
                progress_bar=progress_bar,
                dry_run=dry_run,
            )

        if dry_run:
            return

        if include_self:
            if progress_bar:
                progress_bar.total += 1
                progress_bar.refresh()
            await self._delete_current_entity_acl(
                client=client,
                benefactor_tracker=benefactor_tracker,
                progress_bar=progress_bar,
            )

        if should_process_children:
            if include_container_content:
                if progress_bar:
                    progress_bar.total += 1
                    progress_bar.refresh()
                await self._process_container_contents(
                    client=client,
                    target_entity_types=normalized_types,
                    benefactor_tracker=benefactor_tracker,
                    progress_bar=progress_bar,
                    recursive=recursive,
                    include_container_content=include_container_content,
                )
                if progress_bar:
                    progress_bar.update(1)  # Process container contents complete

list_acl_async async

list_acl_async(recursive: bool = False, include_container_content: bool = False, target_entity_types: Optional[List[str]] = None, log_tree: bool = False, *, synapse_client: Optional[Synapse] = None, _progress_bar: Optional[tqdm] = None) -> AclListResult

List the Access Control Lists (ACLs) for this entity and optionally its children.

This function returns the local sharing settings for the entity and optionally its children. It provides a mapping of all ACLs for the given container/entity.

Important Note: This function returns the LOCAL sharing settings only, not the effective permissions that each Synapse User ID/Team has on the entities. More permissive permissions could be granted via a Team that the user has access to that has permissions on the entity, or through inheritance from parent entities.

PARAMETER DESCRIPTION
recursive

If True and the entity is a container (e.g., Project or Folder), recursively process child containers. Note that this must be used with include_container_content=True to have any effect. Setting recursive=True with include_container_content=False will raise a ValueError. Only works on classes that support the sync_from_synapse_async method.

TYPE: bool DEFAULT: False

include_container_content

If True, include ACLs from contents directly within containers (files and folders inside self). This must be set to True for recursive to have any effect. Defaults to False.

TYPE: bool DEFAULT: False

target_entity_types

Specify which entity types to process when listing ACLs. Allowed values are "folder", "file", "project", "table", "entityview", "materializedview", "virtualtable", "dataset", "datasetcollection", "submissionview" (case-insensitive). If None, defaults to ["folder", "file"].

TYPE: Optional[List[str]] DEFAULT: None

log_tree

If True, logs the ACL results to console in ASCII tree format showing entity hierarchies and their ACL permissions in a tree-like structure. Defaults to False.

TYPE: bool DEFAULT: False

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.

TYPE: Optional[Synapse] DEFAULT: None

_progress_bar

Internal parameter. Progress bar instance to use for updates when called recursively. Should not be used by external callers.

TYPE: Optional[tqdm] DEFAULT: None

RETURNS DESCRIPTION
AclListResult

An AclListResult object containing a structured representation of ACLs where:

AclListResult
  • entity_acls: A list of EntityAcl objects, each representing one entity's ACL
AclListResult
  • Each EntityAcl contains acl_entries (a list of AclEntry objects)
AclListResult
  • Each AclEntry contains the principal_id and their list of permissions
RAISES DESCRIPTION
ValueError

If the entity does not have an ID or if an invalid entity type is provided.

SynapseHTTPError

If there are permission issues accessing ACLs.

Exception

For any other errors that may occur during the process.

List ACLs for a single entity
import asyncio
from synapseclient import Synapse
from synapseclient.models import File

syn = Synapse()
syn.login()

async def main():
    acl_result = await File(id="syn123").list_acl_async()
    print(acl_result)

    # Access entity ACLs (entity_acls is a list, not a dict)
    for entity_acl in acl_result.all_entity_acls:
        if entity_acl.entity_id == "syn123":
            # Access individual ACL entries
            for acl_entry in entity_acl.acl_entries:
                if acl_entry.principal_id == "273948":
                    print(f"Principal 273948 has permissions: {acl_entry.permissions}")

    # I can also access the ACL for the file itself
    print(acl_result.entity_acl)

    print(acl_result)

asyncio.run(main())
List ACLs recursively for a folder and all its children
import asyncio
from synapseclient import Synapse
from synapseclient.models import Folder

syn = Synapse()
syn.login()

async def main():
    acl_result = await Folder(id="syn123").list_acl_async(
        recursive=True,
        include_container_content=True
    )

    # Access each entity's ACL (entity_acls is a list)
    for entity_acl in acl_result.all_entity_acls:
        print(f"Entity {entity_acl.entity_id} has ACL with {len(entity_acl.acl_entries)} principals")

    # I can also access the ACL for the folder itself
    print(acl_result.entity_acl)

    # List ACLs for only folder entities
    folder_acl_result = await Folder(id="syn123").list_acl_async(
        recursive=True,
        include_container_content=True,
        target_entity_types=["folder"]
    )

    # List ACLs for specific entity types (e.g., tables and views)
    table_view_acl_result = await Folder(id="syn123").list_acl_async(
        recursive=True,
        include_container_content=True,
        target_entity_types=["table", "entityview", "materializedview"]
    )

asyncio.run(main())
List ACLs with ASCII tree visualization

When log_tree=True, the ACLs will be logged in a tree format. Additionally, the ascii_tree attribute of the AclListResult will contain the ASCII tree representation of the ACLs.

import asyncio
from synapseclient import Synapse
from synapseclient.models import Folder

syn = Synapse()
syn.login()

async def main():
    acl_result = await Folder(id="syn123").list_acl_async(
        recursive=True,
        include_container_content=True,
        log_tree=True, # Enable ASCII tree logging
    )

    # The ASCII tree representation of the ACLs will also be available
    # in acl_result.ascii_tree
    print(acl_result.ascii_tree)

asyncio.run(main())
Source code in synapseclient/models/mixins/access_control.py
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
async def list_acl_async(
    self,
    recursive: bool = False,
    include_container_content: bool = False,
    target_entity_types: Optional[List[str]] = None,
    log_tree: bool = False,
    *,
    synapse_client: Optional[Synapse] = None,
    _progress_bar: Optional[tqdm] = None,  # Internal parameter for recursive calls
) -> AclListResult:
    """
    List the Access Control Lists (ACLs) for this entity and optionally its children.

    This function returns the local sharing settings for the entity and optionally
    its children. It provides a mapping of all ACLs for the given container/entity.

    **Important Note:** This function returns the LOCAL sharing settings only, not
    the effective permissions that each Synapse User ID/Team has on the entities.
    More permissive permissions could be granted via a Team that the user has access
    to that has permissions on the entity, or through inheritance from parent entities.

    Arguments:
        recursive: If True and the entity is a container (e.g., Project or Folder),
            recursively process child containers. Note that this must be used with
            include_container_content=True to have any effect. Setting recursive=True
            with include_container_content=False will raise a ValueError.
            Only works on classes that support the `sync_from_synapse_async` method.
        include_container_content: If True, include ACLs from contents directly within
            containers (files and folders inside self). This must be set to
            True for recursive to have any effect. Defaults to False.
        target_entity_types: Specify which entity types to process when listing ACLs.
            Allowed values are "folder", "file", "project", "table", "entityview",
            "materializedview", "virtualtable", "dataset", "datasetcollection",
            "submissionview" (case-insensitive). If None, defaults to ["folder", "file"].
        log_tree: If True, logs the ACL results to console in ASCII tree format showing
            entity hierarchies and their ACL permissions in a tree-like structure.
            Defaults to False.
        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.
        _progress_bar: Internal parameter. Progress bar instance to use for updates
            when called recursively. Should not be used by external callers.

    Returns:
        An AclListResult object containing a structured representation of ACLs where:
        - entity_acls: A list of EntityAcl objects, each representing one entity's ACL
        - Each EntityAcl contains acl_entries (a list of AclEntry objects)
        - Each AclEntry contains the principal_id and their list of permissions

    Raises:
        ValueError: If the entity does not have an ID or if an invalid entity type is provided.
        SynapseHTTPError: If there are permission issues accessing ACLs.
        Exception: For any other errors that may occur during the process.

    Example: List ACLs for a single entity
        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import File

        syn = Synapse()
        syn.login()

        async def main():
            acl_result = await File(id="syn123").list_acl_async()
            print(acl_result)

            # Access entity ACLs (entity_acls is a list, not a dict)
            for entity_acl in acl_result.all_entity_acls:
                if entity_acl.entity_id == "syn123":
                    # Access individual ACL entries
                    for acl_entry in entity_acl.acl_entries:
                        if acl_entry.principal_id == "273948":
                            print(f"Principal 273948 has permissions: {acl_entry.permissions}")

            # I can also access the ACL for the file itself
            print(acl_result.entity_acl)

            print(acl_result)

        asyncio.run(main())
        ```

    Example: List ACLs recursively for a folder and all its children
        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Folder

        syn = Synapse()
        syn.login()

        async def main():
            acl_result = await Folder(id="syn123").list_acl_async(
                recursive=True,
                include_container_content=True
            )

            # Access each entity's ACL (entity_acls is a list)
            for entity_acl in acl_result.all_entity_acls:
                print(f"Entity {entity_acl.entity_id} has ACL with {len(entity_acl.acl_entries)} principals")

            # I can also access the ACL for the folder itself
            print(acl_result.entity_acl)

            # List ACLs for only folder entities
            folder_acl_result = await Folder(id="syn123").list_acl_async(
                recursive=True,
                include_container_content=True,
                target_entity_types=["folder"]
            )

            # List ACLs for specific entity types (e.g., tables and views)
            table_view_acl_result = await Folder(id="syn123").list_acl_async(
                recursive=True,
                include_container_content=True,
                target_entity_types=["table", "entityview", "materializedview"]
            )

        asyncio.run(main())
        ```

    Example: List ACLs with ASCII tree visualization
        When `log_tree=True`, the ACLs will be logged in a tree format. Additionally,
        the `ascii_tree` attribute of the AclListResult will contain the ASCII tree
        representation of the ACLs.

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Folder

        syn = Synapse()
        syn.login()

        async def main():
            acl_result = await Folder(id="syn123").list_acl_async(
                recursive=True,
                include_container_content=True,
                log_tree=True, # Enable ASCII tree logging
            )

            # The ASCII tree representation of the ACLs will also be available
            # in acl_result.ascii_tree
            print(acl_result.ascii_tree)

        asyncio.run(main())
        ```
    """
    if not self.id:
        raise ValueError("The entity must have an ID to list ACLs.")

    normalized_types = self._normalize_target_entity_types(target_entity_types)
    client = Synapse.get_client(synapse_client=synapse_client)

    all_acls: Dict[str, Dict[str, List[str]]] = {}
    all_entities = []

    # Only update progress bar for self ACL if we're the top-level call (not recursive)
    # When _progress_bar is passed, it means this is a recursive call and the parent
    # is managing progress updates
    update_progress_for_self = _progress_bar is None
    acl = await self._get_current_entity_acl(
        client=client,
        progress_bar=_progress_bar if update_progress_for_self else None,
    )
    if acl is not None:
        all_acls[self.id] = acl
    all_entities.append(self)

    should_process_children = (recursive or include_container_content) and hasattr(
        self, "sync_from_synapse_async"
    )

    if should_process_children and (recursive and not include_container_content):
        raise ValueError(
            "When recursive=True, include_container_content must also be True. "
            "Setting recursive=True with include_container_content=False has no effect."
        )

    if should_process_children and _progress_bar is None:
        with shared_download_progress_bar(
            file_size=1,
            synapse_client=client,
            custom_message="Collecting ACLs...",
            unit=None,
        ) as progress_bar:
            await self._process_children_with_progress(
                client=client,
                normalized_types=normalized_types,
                include_container_content=include_container_content,
                recursive=recursive,
                all_entities=all_entities,
                all_acls=all_acls,
                progress_bar=progress_bar,
            )
            # Ensure progress bar reaches 100% completion
            if progress_bar:
                remaining = (
                    progress_bar.total - progress_bar.n
                    if progress_bar.total > progress_bar.n
                    else 0
                )
                if remaining > 0:
                    progress_bar.update(remaining)
    elif should_process_children:
        await self._process_children_with_progress(
            client=client,
            normalized_types=normalized_types,
            include_container_content=include_container_content,
            recursive=recursive,
            all_entities=all_entities,
            all_acls=all_acls,
            progress_bar=_progress_bar,
        )
    current_acl = all_acls.get(self.id)
    acl_result = AclListResult.from_dict(
        all_acl_dict=all_acls, current_acl_dict=current_acl
    )

    if log_tree:
        logged_tree = await self._log_acl_tree(acl_result, all_entities, client)
        acl_result.ascii_tree = logged_tree

    return acl_result

bind_schema_async async

bind_schema_async(json_schema_uri: str, *, enable_derived_annotations: bool = False, synapse_client: Optional[Synapse] = None) -> JSONSchemaBinding

Bind a JSON schema to the entity.

PARAMETER DESCRIPTION
json_schema_uri

The URI of the JSON schema to bind to the entity.

TYPE: str

enable_derived_annotations

If true, enable derived annotations. Defaults to False.

TYPE: bool DEFAULT: False

synapse_client

The Synapse client instance. If not provided, the last created instance from the Synapse class constructor will be used.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
JSONSchemaBinding

An object containing details about the JSON schema binding.

Using this function

Binding JSON schema to a folder or a file. This example expects that you have a Synapse project to use, and a file to upload. Set the PROJECT_NAME and FILE_PATH variables to your project name and file path respectively.

import asyncio
from synapseclient import Synapse
from synapseclient.models import File, Folder

syn = Synapse()
syn.login()

# Define Project and JSON schema info
PROJECT_NAME = "test_json_schema_project"  # replace with your project name
FILE_PATH = "~/Sample.txt"  # replace with your test file path

PROJECT_ID = syn.findEntityId(name=PROJECT_NAME)
ORG_NAME = "UniqueOrg"  # replace with your organization name
SCHEMA_NAME = "myTestSchema"  # replace with your schema name
FOLDER_NAME = "test_script_folder"
VERSION = "0.0.1"
SCHEMA_URI = f"{ORG_NAME}-{SCHEMA_NAME}-{VERSION}"

# Create organization (if not already created)
js = syn.service("json_schema")
all_orgs = js.list_organizations()
for org in all_orgs:
    if org["name"] == ORG_NAME:
        print(f"Organization {ORG_NAME} already exists: {org}")
        break
else:
    print(f"Creating organization {ORG_NAME}.")
    created_organization = js.create_organization(ORG_NAME)
    print(f"Created organization: {created_organization}")

my_test_org = js.JsonSchemaOrganization(ORG_NAME)
test_schema = my_test_org.get_json_schema(SCHEMA_NAME)

if not test_schema:
    # Create the schema (if not already created)
    schema_definition = {
        "$id": "mySchema",
        "type": "object",
        "properties": {
            "foo": {"type": "string"},
            "bar": {"type": "integer"},
        },
        "required": ["foo"]
    }
    test_schema = my_test_org.create_json_schema(schema_definition, SCHEMA_NAME, VERSION)
    print(f"Created new schema: {SCHEMA_NAME}")

async def main():
    # Create a test folder
    test_folder = Folder(name=FOLDER_NAME, parent_id=PROJECT_ID)
    await test_folder.store_async()
    print(f"Created test folder: {FOLDER_NAME}")

    # Bind JSON schema to the folder
    bound_schema = await test_folder.bind_schema_async(
        json_schema_uri=SCHEMA_URI,
        enable_derived_annotations=True
    )
    print(f"Result from binding schema to folder: {bound_schema}")

    # Create and bind schema to a file
    example_file = File(
        path=FILE_PATH,  # Replace with your test file path
        parent_id=test_folder.id,
    )
    await example_file.store_async()

    bound_schema_file = await example_file.bind_schema_async(
        json_schema_uri=SCHEMA_URI,
        enable_derived_annotations=True
    )
    print(f"Result from binding schema to file: {bound_schema_file}")

asyncio.run(main())
Source code in synapseclient/models/mixins/json_schema.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
async def bind_schema_async(
    self,
    json_schema_uri: str,
    *,
    enable_derived_annotations: bool = False,
    synapse_client: Optional["Synapse"] = None,
) -> JSONSchemaBinding:
    """
    Bind a JSON schema to the entity.

    Arguments:
        json_schema_uri: The URI of the JSON schema to bind to the entity.
        enable_derived_annotations: If true, enable derived annotations. Defaults to False.
        synapse_client: The Synapse client instance. If not provided,
            the last created instance from the Synapse class constructor will be used.

    Returns:
        An object containing details about the JSON schema binding.

    Example: Using this function
        Binding JSON schema to a folder or a file. This example expects that you
        have a Synapse project to use, and a file to upload. Set the `PROJECT_NAME`
        and `FILE_PATH` variables to your project name and file path respectively.

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import File, Folder

        syn = Synapse()
        syn.login()

        # Define Project and JSON schema info
        PROJECT_NAME = "test_json_schema_project"  # replace with your project name
        FILE_PATH = "~/Sample.txt"  # replace with your test file path

        PROJECT_ID = syn.findEntityId(name=PROJECT_NAME)
        ORG_NAME = "UniqueOrg"  # replace with your organization name
        SCHEMA_NAME = "myTestSchema"  # replace with your schema name
        FOLDER_NAME = "test_script_folder"
        VERSION = "0.0.1"
        SCHEMA_URI = f"{ORG_NAME}-{SCHEMA_NAME}-{VERSION}"

        # Create organization (if not already created)
        js = syn.service("json_schema")
        all_orgs = js.list_organizations()
        for org in all_orgs:
            if org["name"] == ORG_NAME:
                print(f"Organization {ORG_NAME} already exists: {org}")
                break
        else:
            print(f"Creating organization {ORG_NAME}.")
            created_organization = js.create_organization(ORG_NAME)
            print(f"Created organization: {created_organization}")

        my_test_org = js.JsonSchemaOrganization(ORG_NAME)
        test_schema = my_test_org.get_json_schema(SCHEMA_NAME)

        if not test_schema:
            # Create the schema (if not already created)
            schema_definition = {
                "$id": "mySchema",
                "type": "object",
                "properties": {
                    "foo": {"type": "string"},
                    "bar": {"type": "integer"},
                },
                "required": ["foo"]
            }
            test_schema = my_test_org.create_json_schema(schema_definition, SCHEMA_NAME, VERSION)
            print(f"Created new schema: {SCHEMA_NAME}")

        async def main():
            # Create a test folder
            test_folder = Folder(name=FOLDER_NAME, parent_id=PROJECT_ID)
            await test_folder.store_async()
            print(f"Created test folder: {FOLDER_NAME}")

            # Bind JSON schema to the folder
            bound_schema = await test_folder.bind_schema_async(
                json_schema_uri=SCHEMA_URI,
                enable_derived_annotations=True
            )
            print(f"Result from binding schema to folder: {bound_schema}")

            # Create and bind schema to a file
            example_file = File(
                path=FILE_PATH,  # Replace with your test file path
                parent_id=test_folder.id,
            )
            await example_file.store_async()

            bound_schema_file = await example_file.bind_schema_async(
                json_schema_uri=SCHEMA_URI,
                enable_derived_annotations=True
            )
            print(f"Result from binding schema to file: {bound_schema_file}")

        asyncio.run(main())
        ```
    """
    response = await bind_json_schema_to_entity(
        synapse_id=self.id,
        json_schema_uri=json_schema_uri,
        enable_derived_annotations=enable_derived_annotations,
        synapse_client=synapse_client,
    )
    json_schema_version = response.get("jsonSchemaVersionInfo", {})
    return JSONSchemaBinding(
        json_schema_version_info=JSONSchemaVersionInfo(
            organization_id=json_schema_version.get("organizationId", None),
            organization_name=json_schema_version.get("organizationName", None),
            schema_id=json_schema_version.get("schemaId", None),
            id=json_schema_version.get("$id", None),
            schema_name=json_schema_version.get("schemaName", None),
            version_id=json_schema_version.get("versionId", None),
            semantic_version=json_schema_version.get("semanticVersion", None),
            json_sha256_hex=json_schema_version.get("jsonSHA256Hex", None),
            created_on=json_schema_version.get("createdOn", None),
            created_by=json_schema_version.get("createdBy", None),
        ),
        object_id=response.get("objectId", None),
        object_type=response.get("objectType", None),
        created_on=response.get("createdOn", None),
        created_by=response.get("createdBy", None),
        enable_derived_annotations=response.get("enableDerivedAnnotations", None),
    )

get_schema_async async

get_schema_async(*, synapse_client: Optional[Synapse] = None) -> JSONSchemaBinding

Get the JSON schema bound to the entity.

PARAMETER DESCRIPTION
synapse_client

The Synapse client instance. If not provided, the last created instance from the Synapse class constructor will be used.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
JSONSchemaBinding

An object containing details about the bound JSON schema.

Using this function

Retrieving the bound JSON schema from a folder or file. This example demonstrates how to get existing schema bindings from entities that already have schemas bound. Set the PROJECT_NAME and FILE_PATH variables to your project name and file path respectively.

import asyncio
from synapseclient import Synapse
from synapseclient.models import File, Folder

syn = Synapse()
syn.login()

# Define Project and JSON schema info
PROJECT_NAME = "test_json_schema_project"  # replace with your project name
FILE_PATH = "~/Sample.txt"  # replace with your test file path

PROJECT_ID = syn.findEntityId(name=PROJECT_NAME)
ORG_NAME = "UniqueOrg"  # replace with your organization name
SCHEMA_NAME = "myTestSchema"  # replace with your schema name
FOLDER_NAME = "test_script_folder"
VERSION = "0.0.1"
SCHEMA_URI = f"{ORG_NAME}-{SCHEMA_NAME}-{VERSION}"

# Create organization (if not already created)
js = syn.service("json_schema")
all_orgs = js.list_organizations()
for org in all_orgs:
    if org["name"] == ORG_NAME:
        print(f"Organization {ORG_NAME} already exists: {org}")
        break
else:
    print(f"Creating organization {ORG_NAME}.")
    created_organization = js.create_organization(ORG_NAME)
    print(f"Created organization: {created_organization}")

my_test_org = js.JsonSchemaOrganization(ORG_NAME)
test_schema = my_test_org.get_json_schema(SCHEMA_NAME)

if not test_schema:
    # Create the schema (if not already created)
    schema_definition = {
        "$id": "mySchema",
        "type": "object",
        "properties": {
            "foo": {"type": "string"},
            "bar": {"type": "integer"},
        },
        "required": ["foo"]
    }
    test_schema = my_test_org.create_json_schema(schema_definition, SCHEMA_NAME, VERSION)
    print(f"Created new schema: {SCHEMA_NAME}")

async def main():
    # Create a test folder
    test_folder = Folder(name=FOLDER_NAME, parent_id=PROJECT_ID)
    await test_folder.store_async()
    print(f"Created test folder: {FOLDER_NAME}")

    # Bind JSON schema to the folder first
    bound_schema = await test_folder.bind_schema_async(
        json_schema_uri=SCHEMA_URI,
        enable_derived_annotations=True
    )
    print(f"Bound schema to folder: {bound_schema}")

    # Create and bind schema to a file
    example_file = File(
        path=FILE_PATH,  # Replace with your test file path
        parent_id=test_folder.id,
    )
    await example_file.store_async()

    bound_schema_file = await example_file.bind_schema_async(
        json_schema_uri=SCHEMA_URI,
        enable_derived_annotations=True
    )
    print(f"Bound schema to file: {bound_schema_file}")

    # Retrieve the bound schema from the folder
    bound_schema = await test_folder.get_schema_async()
    print(f"Retrieved schema from folder: {bound_schema}")

    # Retrieve the bound schema from the file
    bound_schema_file = await example_file.get_schema_async()
    print(f"Retrieved schema from file: {bound_schema_file}")

asyncio.run(main())
Source code in synapseclient/models/mixins/json_schema.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
async def get_schema_async(
    self, *, synapse_client: Optional["Synapse"] = None
) -> JSONSchemaBinding:
    """
    Get the JSON schema bound to the entity.

    Arguments:
        synapse_client: The Synapse client instance. If not provided,
            the last created instance from the Synapse class constructor will be used.

    Returns:
        An object containing details about the bound JSON schema.

    Example: Using this function
        Retrieving the bound JSON schema from a folder or file. This example demonstrates
        how to get existing schema bindings from entities that already have schemas bound.
        Set the `PROJECT_NAME` and `FILE_PATH` variables to your project name
        and file path respectively.

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import File, Folder

        syn = Synapse()
        syn.login()

        # Define Project and JSON schema info
        PROJECT_NAME = "test_json_schema_project"  # replace with your project name
        FILE_PATH = "~/Sample.txt"  # replace with your test file path

        PROJECT_ID = syn.findEntityId(name=PROJECT_NAME)
        ORG_NAME = "UniqueOrg"  # replace with your organization name
        SCHEMA_NAME = "myTestSchema"  # replace with your schema name
        FOLDER_NAME = "test_script_folder"
        VERSION = "0.0.1"
        SCHEMA_URI = f"{ORG_NAME}-{SCHEMA_NAME}-{VERSION}"

        # Create organization (if not already created)
        js = syn.service("json_schema")
        all_orgs = js.list_organizations()
        for org in all_orgs:
            if org["name"] == ORG_NAME:
                print(f"Organization {ORG_NAME} already exists: {org}")
                break
        else:
            print(f"Creating organization {ORG_NAME}.")
            created_organization = js.create_organization(ORG_NAME)
            print(f"Created organization: {created_organization}")

        my_test_org = js.JsonSchemaOrganization(ORG_NAME)
        test_schema = my_test_org.get_json_schema(SCHEMA_NAME)

        if not test_schema:
            # Create the schema (if not already created)
            schema_definition = {
                "$id": "mySchema",
                "type": "object",
                "properties": {
                    "foo": {"type": "string"},
                    "bar": {"type": "integer"},
                },
                "required": ["foo"]
            }
            test_schema = my_test_org.create_json_schema(schema_definition, SCHEMA_NAME, VERSION)
            print(f"Created new schema: {SCHEMA_NAME}")

        async def main():
            # Create a test folder
            test_folder = Folder(name=FOLDER_NAME, parent_id=PROJECT_ID)
            await test_folder.store_async()
            print(f"Created test folder: {FOLDER_NAME}")

            # Bind JSON schema to the folder first
            bound_schema = await test_folder.bind_schema_async(
                json_schema_uri=SCHEMA_URI,
                enable_derived_annotations=True
            )
            print(f"Bound schema to folder: {bound_schema}")

            # Create and bind schema to a file
            example_file = File(
                path=FILE_PATH,  # Replace with your test file path
                parent_id=test_folder.id,
            )
            await example_file.store_async()

            bound_schema_file = await example_file.bind_schema_async(
                json_schema_uri=SCHEMA_URI,
                enable_derived_annotations=True
            )
            print(f"Bound schema to file: {bound_schema_file}")

            # Retrieve the bound schema from the folder
            bound_schema = await test_folder.get_schema_async()
            print(f"Retrieved schema from folder: {bound_schema}")

            # Retrieve the bound schema from the file
            bound_schema_file = await example_file.get_schema_async()
            print(f"Retrieved schema from file: {bound_schema_file}")

        asyncio.run(main())
        ```
    """
    response = await get_json_schema_from_entity(
        synapse_id=self.id, synapse_client=synapse_client
    )
    json_schema_version_info = response.get("jsonSchemaVersionInfo", {})
    return JSONSchemaBinding(
        json_schema_version_info=JSONSchemaVersionInfo(
            organization_id=json_schema_version_info.get("organizationId", None),
            organization_name=json_schema_version_info.get(
                "organizationName", None
            ),
            schema_id=json_schema_version_info.get("schemaId", None),
            id=json_schema_version_info.get("$id", None),
            schema_name=json_schema_version_info.get("schemaName", None),
            version_id=json_schema_version_info.get("versionId", None),
            semantic_version=json_schema_version_info.get("semanticVersion", None),
            json_sha256_hex=json_schema_version_info.get("jsonSHA256Hex", None),
            created_on=json_schema_version_info.get("createdOn", None),
            created_by=json_schema_version_info.get("createdBy", None),
        ),
        object_id=response.get("objectId", None),
        object_type=response.get("objectType", None),
        created_on=response.get("createdOn", None),
        created_by=response.get("createdBy", None),
        enable_derived_annotations=response.get("enableDerivedAnnotations", None),
    )

unbind_schema_async async

unbind_schema_async(*, synapse_client: Optional[Synapse] = None) -> None

Unbind the JSON schema bound to the entity.

PARAMETER DESCRIPTION
synapse_client

The Synapse client instance. If not provided, the last created instance from the Synapse class constructor will be used.

TYPE: Optional[Synapse] DEFAULT: None

Using this function

Unbinding a JSON schema from a folder or file. This example demonstrates how to remove schema bindings from entities. Assumes entities already have schemas bound. Set the PROJECT_NAME and FILE_PATH variables to your project name and file path respectively.

import asyncio
from synapseclient import Synapse
from synapseclient.models import File, Folder

syn = Synapse()
syn.login()

# Define Project and JSON schema info
PROJECT_NAME = "test_json_schema_project"  # replace with your project name
FILE_PATH = "~/Sample.txt"  # replace with your test file path

PROJECT_ID = syn.findEntityId(name=PROJECT_NAME)
ORG_NAME = "UniqueOrg"  # replace with your organization name
SCHEMA_NAME = "myTestSchema"  # replace with your schema name
FOLDER_NAME = "test_script_folder"
VERSION = "0.0.1"
SCHEMA_URI = f"{ORG_NAME}-{SCHEMA_NAME}-{VERSION}"

# Create organization (if not already created)
js = syn.service("json_schema")
all_orgs = js.list_organizations()
for org in all_orgs:
    if org["name"] == ORG_NAME:
        print(f"Organization {ORG_NAME} already exists: {org}")
        break
else:
    print(f"Creating organization {ORG_NAME}.")
    created_organization = js.create_organization(ORG_NAME)
    print(f"Created organization: {created_organization}")

my_test_org = js.JsonSchemaOrganization(ORG_NAME)
test_schema = my_test_org.get_json_schema(SCHEMA_NAME)

if not test_schema:
    # Create the schema (if not already created)
    schema_definition = {
        "$id": "mySchema",
        "type": "object",
        "properties": {
            "foo": {"type": "string"},
            "bar": {"type": "integer"},
        },
        "required": ["foo"]
    }
    test_schema = my_test_org.create_json_schema(schema_definition, SCHEMA_NAME, VERSION)
    print(f"Created new schema: {SCHEMA_NAME}")

async def main():
    # Create a test folder
    test_folder = Folder(name=FOLDER_NAME, parent_id=PROJECT_ID)
    await test_folder.store_async()
    print(f"Created test folder: {FOLDER_NAME}")

    # Bind JSON schema to the folder first
    bound_schema = await test_folder.bind_schema_async(
        json_schema_uri=SCHEMA_URI,
        enable_derived_annotations=True
    )
    print(f"Bound schema to folder: {bound_schema}")

    # Create and bind schema to a file
    example_file = File(
        path=FILE_PATH,  # Replace with your test file path
        parent_id=test_folder.id,
    )
    await example_file.store_async()
    print(f"Created test file: {FILE_PATH}")

    bound_schema_file = await example_file.bind_schema_async(
        json_schema_uri=SCHEMA_URI,
        enable_derived_annotations=True
    )
    print(f"Bound schema to file: {bound_schema_file}")

    # Unbind the schema from the folder
    await test_folder.unbind_schema_async()
    print("Successfully unbound schema from folder")

    # Unbind the schema from the file
    await example_file.unbind_schema_async()
    print("Successfully unbound schema from file")

asyncio.run(main())
Source code in synapseclient/models/mixins/json_schema.py
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
async def unbind_schema_async(
    self, *, synapse_client: Optional["Synapse"] = None
) -> None:
    """
    Unbind the JSON schema bound to the entity.

    Arguments:
        synapse_client: The Synapse client instance. If not provided,
            the last created instance from the Synapse class constructor will be used.

    Example: Using this function
        Unbinding a JSON schema from a folder or file. This example demonstrates
        how to remove schema bindings from entities. Assumes entities already have
        schemas bound. Set the `PROJECT_NAME` and `FILE_PATH` variables to your
        project name and file path respectively.


        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import File, Folder

        syn = Synapse()
        syn.login()

        # Define Project and JSON schema info
        PROJECT_NAME = "test_json_schema_project"  # replace with your project name
        FILE_PATH = "~/Sample.txt"  # replace with your test file path

        PROJECT_ID = syn.findEntityId(name=PROJECT_NAME)
        ORG_NAME = "UniqueOrg"  # replace with your organization name
        SCHEMA_NAME = "myTestSchema"  # replace with your schema name
        FOLDER_NAME = "test_script_folder"
        VERSION = "0.0.1"
        SCHEMA_URI = f"{ORG_NAME}-{SCHEMA_NAME}-{VERSION}"

        # Create organization (if not already created)
        js = syn.service("json_schema")
        all_orgs = js.list_organizations()
        for org in all_orgs:
            if org["name"] == ORG_NAME:
                print(f"Organization {ORG_NAME} already exists: {org}")
                break
        else:
            print(f"Creating organization {ORG_NAME}.")
            created_organization = js.create_organization(ORG_NAME)
            print(f"Created organization: {created_organization}")

        my_test_org = js.JsonSchemaOrganization(ORG_NAME)
        test_schema = my_test_org.get_json_schema(SCHEMA_NAME)

        if not test_schema:
            # Create the schema (if not already created)
            schema_definition = {
                "$id": "mySchema",
                "type": "object",
                "properties": {
                    "foo": {"type": "string"},
                    "bar": {"type": "integer"},
                },
                "required": ["foo"]
            }
            test_schema = my_test_org.create_json_schema(schema_definition, SCHEMA_NAME, VERSION)
            print(f"Created new schema: {SCHEMA_NAME}")

        async def main():
            # Create a test folder
            test_folder = Folder(name=FOLDER_NAME, parent_id=PROJECT_ID)
            await test_folder.store_async()
            print(f"Created test folder: {FOLDER_NAME}")

            # Bind JSON schema to the folder first
            bound_schema = await test_folder.bind_schema_async(
                json_schema_uri=SCHEMA_URI,
                enable_derived_annotations=True
            )
            print(f"Bound schema to folder: {bound_schema}")

            # Create and bind schema to a file
            example_file = File(
                path=FILE_PATH,  # Replace with your test file path
                parent_id=test_folder.id,
            )
            await example_file.store_async()
            print(f"Created test file: {FILE_PATH}")

            bound_schema_file = await example_file.bind_schema_async(
                json_schema_uri=SCHEMA_URI,
                enable_derived_annotations=True
            )
            print(f"Bound schema to file: {bound_schema_file}")

            # Unbind the schema from the folder
            await test_folder.unbind_schema_async()
            print("Successfully unbound schema from folder")

            # Unbind the schema from the file
            await example_file.unbind_schema_async()
            print("Successfully unbound schema from file")

        asyncio.run(main())
        ```
    """
    return await delete_json_schema_from_entity(
        synapse_id=self.id, synapse_client=synapse_client
    )

validate_schema_async async

validate_schema_async(*, synapse_client: Optional[Synapse] = None) -> Union[JSONSchemaValidation, InvalidJSONSchemaValidation]

Validate the entity against the bound JSON schema.

PARAMETER DESCRIPTION
synapse_client

The Synapse client instance. If not provided, the last created instance from the Synapse class constructor will be used.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
Union[JSONSchemaValidation, InvalidJSONSchemaValidation]

The validation results.

Using this function

Validating a folder or file against the bound JSON schema. This example demonstrates how to validate entities with annotations against their bound schemas. Requires entities to have schemas already bound. Set the PROJECT_NAME and FILE_PATH variables to your project name and file path respectively.

import asyncio
import time
from synapseclient import Synapse
from synapseclient.models import File, Folder

syn = Synapse()
syn.login()

# Define Project and JSON schema info
PROJECT_NAME = "test_json_schema_project"  # replace with your project name
FILE_PATH = "~/Sample.txt"  # replace with your test file path

PROJECT_ID = syn.findEntityId(name=PROJECT_NAME)
ORG_NAME = "UniqueOrg"  # replace with your organization name
SCHEMA_NAME = "myTestSchema"  # replace with your schema name
FOLDER_NAME = "test_script_folder"
VERSION = "0.0.1"
SCHEMA_URI = f"{ORG_NAME}-{SCHEMA_NAME}-{VERSION}"

# Create organization (if not already created)
js = syn.service("json_schema")
all_orgs = js.list_organizations()
for org in all_orgs:
    if org["name"] == ORG_NAME:
        print(f"Organization {ORG_NAME} already exists: {org}")
        break
else:
    print(f"Creating organization {ORG_NAME}.")
    created_organization = js.create_organization(ORG_NAME)
    print(f"Created organization: {created_organization}")

my_test_org = js.JsonSchemaOrganization(ORG_NAME)
test_schema = my_test_org.get_json_schema(SCHEMA_NAME)

if not test_schema:
    # Create the schema (if not already created)
    schema_definition = {
        "$id": "mySchema",
        "type": "object",
        "properties": {
            "foo": {"type": "string"},
            "bar": {"type": "integer"},
        },
        "required": ["foo"]
    }
    test_schema = my_test_org.create_json_schema(schema_definition, SCHEMA_NAME, VERSION)
    print(f"Created new schema: {SCHEMA_NAME}")

async def main():
    # Create a test folder
    test_folder = Folder(name=FOLDER_NAME, parent_id=PROJECT_ID)
    await test_folder.store_async()
    print(f"Created test folder: {FOLDER_NAME}")

    # Bind JSON schema to the folder
    bound_schema = await test_folder.bind_schema_async(
        json_schema_uri=SCHEMA_URI,
        enable_derived_annotations=True
    )
    print(f"Bound schema to folder: {bound_schema}")

    # Create and bind schema to a file
    example_file = File(
        path=FILE_PATH,  # Replace with your test file path
        parent_id=test_folder.id,
    )
    await example_file.store_async()

    bound_schema_file = await example_file.bind_schema_async(
        json_schema_uri=SCHEMA_URI,
        enable_derived_annotations=True
    )
    print(f"Bound schema to file: {bound_schema_file}")

    # Validate the folder entity against the bound schema
    test_folder.annotations = {"foo": "test_value", "bar": 42}  # Example annotations
    await test_folder.store_async()
    print("Added annotations to folder and stored")
    time.sleep(2)  # Allow time for processing

    validation_response = await test_folder.validate_schema_async()
    print(f"Folder validation response: {validation_response}")

    # Validate the file entity against the bound schema
    example_file.annotations = {"foo": "test_value", "bar": 43}  # Example annotations
    await example_file.store_async()
    print("Added annotations to file and stored")
    time.sleep(2)  # Allow time for processing

    validation_response_file = await example_file.validate_schema_async()
    print(f"File validation response: {validation_response_file}")

asyncio.run(main())
Source code in synapseclient/models/mixins/json_schema.py
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
async def validate_schema_async(
    self, *, synapse_client: Optional["Synapse"] = None
) -> Union[JSONSchemaValidation, InvalidJSONSchemaValidation]:
    """
    Validate the entity against the bound JSON schema.

    Arguments:
        synapse_client (Optional[Synapse], optional): The Synapse client instance. If not provided,
            the last created instance from the Synapse class constructor will be used.

    Returns:
        The validation results.

    Example: Using this function
        Validating a folder or file against the bound JSON schema. This example demonstrates
        how to validate entities with annotations against their bound schemas. Requires entities
        to have schemas already bound. Set the `PROJECT_NAME` and `FILE_PATH` variables to your project name
        and file path respectively.

        ```python
        import asyncio
        import time
        from synapseclient import Synapse
        from synapseclient.models import File, Folder

        syn = Synapse()
        syn.login()

        # Define Project and JSON schema info
        PROJECT_NAME = "test_json_schema_project"  # replace with your project name
        FILE_PATH = "~/Sample.txt"  # replace with your test file path

        PROJECT_ID = syn.findEntityId(name=PROJECT_NAME)
        ORG_NAME = "UniqueOrg"  # replace with your organization name
        SCHEMA_NAME = "myTestSchema"  # replace with your schema name
        FOLDER_NAME = "test_script_folder"
        VERSION = "0.0.1"
        SCHEMA_URI = f"{ORG_NAME}-{SCHEMA_NAME}-{VERSION}"

        # Create organization (if not already created)
        js = syn.service("json_schema")
        all_orgs = js.list_organizations()
        for org in all_orgs:
            if org["name"] == ORG_NAME:
                print(f"Organization {ORG_NAME} already exists: {org}")
                break
        else:
            print(f"Creating organization {ORG_NAME}.")
            created_organization = js.create_organization(ORG_NAME)
            print(f"Created organization: {created_organization}")

        my_test_org = js.JsonSchemaOrganization(ORG_NAME)
        test_schema = my_test_org.get_json_schema(SCHEMA_NAME)

        if not test_schema:
            # Create the schema (if not already created)
            schema_definition = {
                "$id": "mySchema",
                "type": "object",
                "properties": {
                    "foo": {"type": "string"},
                    "bar": {"type": "integer"},
                },
                "required": ["foo"]
            }
            test_schema = my_test_org.create_json_schema(schema_definition, SCHEMA_NAME, VERSION)
            print(f"Created new schema: {SCHEMA_NAME}")

        async def main():
            # Create a test folder
            test_folder = Folder(name=FOLDER_NAME, parent_id=PROJECT_ID)
            await test_folder.store_async()
            print(f"Created test folder: {FOLDER_NAME}")

            # Bind JSON schema to the folder
            bound_schema = await test_folder.bind_schema_async(
                json_schema_uri=SCHEMA_URI,
                enable_derived_annotations=True
            )
            print(f"Bound schema to folder: {bound_schema}")

            # Create and bind schema to a file
            example_file = File(
                path=FILE_PATH,  # Replace with your test file path
                parent_id=test_folder.id,
            )
            await example_file.store_async()

            bound_schema_file = await example_file.bind_schema_async(
                json_schema_uri=SCHEMA_URI,
                enable_derived_annotations=True
            )
            print(f"Bound schema to file: {bound_schema_file}")

            # Validate the folder entity against the bound schema
            test_folder.annotations = {"foo": "test_value", "bar": 42}  # Example annotations
            await test_folder.store_async()
            print("Added annotations to folder and stored")
            time.sleep(2)  # Allow time for processing

            validation_response = await test_folder.validate_schema_async()
            print(f"Folder validation response: {validation_response}")

            # Validate the file entity against the bound schema
            example_file.annotations = {"foo": "test_value", "bar": 43}  # Example annotations
            await example_file.store_async()
            print("Added annotations to file and stored")
            time.sleep(2)  # Allow time for processing

            validation_response_file = await example_file.validate_schema_async()
            print(f"File validation response: {validation_response_file}")

        asyncio.run(main())
        ```
    """
    response = await validate_entity_with_json_schema(
        synapse_id=self.id, synapse_client=synapse_client
    )
    if "validationException" in response:
        return InvalidJSONSchemaValidation(
            validation_response=JSONSchemaValidation(
                object_id=response.get("objectId", None),
                object_type=response.get("objectType", None),
                object_etag=response.get("objectEtag", None),
                id=response.get("schema$id", None),
                is_valid=response.get("isValid", None),
                validated_on=response.get("validatedOn", None),
            ),
            validation_error_message=response.get("validationErrorMessage", None),
            all_validation_messages=response.get("allValidationMessages", []),
            validation_exception=ValidationException(
                pointer_to_violation=response.get("validationException", {}).get(
                    "pointerToViolation", None
                ),
                message=response.get("validationException", {}).get(
                    "message", None
                ),
                schema_location=response.get("validationException", {}).get(
                    "schemaLocation", None
                ),
                causing_exceptions=[
                    CausingException(
                        keyword=ce.get("keyword", None),
                        pointer_to_violation=ce.get("pointerToViolation", None),
                        message=ce.get("message", None),
                        schema_location=ce.get("schemaLocation", None),
                        causing_exceptions=[
                            CausingException(
                                keyword=nce.get("keyword", None),
                                pointer_to_violation=nce.get(
                                    "pointerToViolation", None
                                ),
                                message=nce.get("message", None),
                                schema_location=nce.get("schemaLocation", None),
                            )
                            for nce in ce.get("causingExceptions", [])
                        ],
                    )
                    for ce in response.get("validationException", {}).get(
                        "causingExceptions", []
                    )
                ],
            ),
        )
    return JSONSchemaValidation(
        object_id=response.get("objectId", None),
        object_type=response.get("objectType", None),
        object_etag=response.get("objectEtag", None),
        id=response.get("schema$id", None),
        is_valid=response.get("isValid", None),
        validated_on=response.get("validatedOn", None),
    )

get_schema_derived_keys_async async

get_schema_derived_keys_async(*, synapse_client: Optional[Synapse] = None) -> JSONSchemaDerivedKeys

Retrieve derived JSON schema keys for the entity.

PARAMETER DESCRIPTION
synapse_client

The Synapse client instance. If not provided, the last created instance from the Synapse class constructor will be used.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
JSONSchemaDerivedKeys

An object containing the derived keys for the entity.

Using this function

Retrieving derived keys from a folder or file. This example demonstrates how to get derived annotation keys from schemas with constant values. Set the PROJECT_NAME variable to your project name.

import asyncio
from synapseclient import Synapse
from synapseclient.models import File, Folder

syn = Synapse()
syn.login()

# Define Project and JSON schema info
PROJECT_NAME = "test_json_schema_project"  # replace with your project name
FILE_PATH = "~/Sample.txt"  # replace with your test file path

PROJECT_ID = syn.findEntityId(name=PROJECT_NAME)
ORG_NAME = "UniqueOrg"  # replace with your organization name
DERIVED_TEST_SCHEMA_NAME = "myTestDerivedSchema"  # replace with your derived schema name
FOLDER_NAME = "test_script_folder"
VERSION = "0.0.1"
SCHEMA_URI = f"{ORG_NAME}-{DERIVED_TEST_SCHEMA_NAME}-{VERSION}"

# Create organization (if not already created)
js = syn.service("json_schema")
all_orgs = js.list_organizations()
for org in all_orgs:
    if org["name"] == ORG_NAME:
        print(f"Organization {ORG_NAME} already exists: {org}")
        break
else:
    print(f"Creating organization {ORG_NAME}.")
    created_organization = js.create_organization(ORG_NAME)
    print(f"Created organization: {created_organization}")

my_test_org = js.JsonSchemaOrganization(ORG_NAME)
test_schema = my_test_org.get_json_schema(DERIVED_TEST_SCHEMA_NAME)

if not test_schema:
    # Create the schema (if not already created)
    schema_definition = {
        "$id": "mySchema",
        "type": "object",
        "properties": {
            "foo": {"type": "string"},
            "baz": {"type": "string", "const": "example_value"},  # Example constant for derived annotation
            "bar": {"type": "integer"},
        },
        "required": ["foo"]
    }
    test_schema = my_test_org.create_json_schema(schema_definition, DERIVED_TEST_SCHEMA_NAME, VERSION)
    print(f"Created new derived schema: {DERIVED_TEST_SCHEMA_NAME}")

async def main():
    # Create a test folder
    test_folder = Folder(name=FOLDER_NAME, parent_id=PROJECT_ID)
    await test_folder.store_async()
    print(f"Created test folder: {FOLDER_NAME}")

    # Bind JSON schema to the folder
    bound_schema = await test_folder.bind_schema_async(
        json_schema_uri=SCHEMA_URI,
        enable_derived_annotations=True
    )
    print(f"Bound schema to folder with derived annotations: {bound_schema}")

    # Create and bind schema to a file
    example_file = File(
        path=FILE_PATH,  # Replace with your test file path
        parent_id=test_folder.id,
    )
    await example_file.store_async()

    bound_schema_file = await example_file.bind_schema_async(
        json_schema_uri=SCHEMA_URI,
        enable_derived_annotations=True
    )
    print(f"Bound schema to file with derived annotations: {bound_schema_file}")

    # Get the derived keys from the bound schema of the folder
    test_folder.annotations = {"foo": "test_value_new", "bar": 42}  # Example annotations
    await test_folder.store_async()
    print("Added annotations to folder and stored")

    derived_keys = await test_folder.get_schema_derived_keys_async()
    print(f"Derived keys from folder: {derived_keys}")

    # Get the derived keys from the bound schema of the file
    example_file.annotations = {"foo": "test_value_new", "bar": 43}  # Example annotations
    await example_file.store_async()
    print("Added annotations to file and stored")

    derived_keys_file = await example_file.get_schema_derived_keys_async()
    print(f"Derived keys from file: {derived_keys_file}")

asyncio.run(main())
Source code in synapseclient/models/mixins/json_schema.py
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
async def get_schema_derived_keys_async(
    self, *, synapse_client: Optional["Synapse"] = None
) -> JSONSchemaDerivedKeys:
    """
    Retrieve derived JSON schema keys for the entity.

    Arguments:
        synapse_client (Optional[Synapse], optional): The Synapse client instance. If not provided,
            the last created instance from the Synapse class constructor will be used.

    Returns:
        An object containing the derived keys for the entity.

    Example: Using this function
        Retrieving derived keys from a folder or file. This example demonstrates
        how to get derived annotation keys from schemas with constant values.
        Set the `PROJECT_NAME` variable to your project name.

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import File, Folder

        syn = Synapse()
        syn.login()

        # Define Project and JSON schema info
        PROJECT_NAME = "test_json_schema_project"  # replace with your project name
        FILE_PATH = "~/Sample.txt"  # replace with your test file path

        PROJECT_ID = syn.findEntityId(name=PROJECT_NAME)
        ORG_NAME = "UniqueOrg"  # replace with your organization name
        DERIVED_TEST_SCHEMA_NAME = "myTestDerivedSchema"  # replace with your derived schema name
        FOLDER_NAME = "test_script_folder"
        VERSION = "0.0.1"
        SCHEMA_URI = f"{ORG_NAME}-{DERIVED_TEST_SCHEMA_NAME}-{VERSION}"

        # Create organization (if not already created)
        js = syn.service("json_schema")
        all_orgs = js.list_organizations()
        for org in all_orgs:
            if org["name"] == ORG_NAME:
                print(f"Organization {ORG_NAME} already exists: {org}")
                break
        else:
            print(f"Creating organization {ORG_NAME}.")
            created_organization = js.create_organization(ORG_NAME)
            print(f"Created organization: {created_organization}")

        my_test_org = js.JsonSchemaOrganization(ORG_NAME)
        test_schema = my_test_org.get_json_schema(DERIVED_TEST_SCHEMA_NAME)

        if not test_schema:
            # Create the schema (if not already created)
            schema_definition = {
                "$id": "mySchema",
                "type": "object",
                "properties": {
                    "foo": {"type": "string"},
                    "baz": {"type": "string", "const": "example_value"},  # Example constant for derived annotation
                    "bar": {"type": "integer"},
                },
                "required": ["foo"]
            }
            test_schema = my_test_org.create_json_schema(schema_definition, DERIVED_TEST_SCHEMA_NAME, VERSION)
            print(f"Created new derived schema: {DERIVED_TEST_SCHEMA_NAME}")

        async def main():
            # Create a test folder
            test_folder = Folder(name=FOLDER_NAME, parent_id=PROJECT_ID)
            await test_folder.store_async()
            print(f"Created test folder: {FOLDER_NAME}")

            # Bind JSON schema to the folder
            bound_schema = await test_folder.bind_schema_async(
                json_schema_uri=SCHEMA_URI,
                enable_derived_annotations=True
            )
            print(f"Bound schema to folder with derived annotations: {bound_schema}")

            # Create and bind schema to a file
            example_file = File(
                path=FILE_PATH,  # Replace with your test file path
                parent_id=test_folder.id,
            )
            await example_file.store_async()

            bound_schema_file = await example_file.bind_schema_async(
                json_schema_uri=SCHEMA_URI,
                enable_derived_annotations=True
            )
            print(f"Bound schema to file with derived annotations: {bound_schema_file}")

            # Get the derived keys from the bound schema of the folder
            test_folder.annotations = {"foo": "test_value_new", "bar": 42}  # Example annotations
            await test_folder.store_async()
            print("Added annotations to folder and stored")

            derived_keys = await test_folder.get_schema_derived_keys_async()
            print(f"Derived keys from folder: {derived_keys}")

            # Get the derived keys from the bound schema of the file
            example_file.annotations = {"foo": "test_value_new", "bar": 43}  # Example annotations
            await example_file.store_async()
            print("Added annotations to file and stored")

            derived_keys_file = await example_file.get_schema_derived_keys_async()
            print(f"Derived keys from file: {derived_keys_file}")

        asyncio.run(main())
        ```
    """
    response = await get_json_schema_derived_keys(
        synapse_id=self.id, synapse_client=synapse_client
    )
    return JSONSchemaDerivedKeys(keys=response["keys"])

synapseclient.models.RecordBasedMetadataTaskProperties dataclass

Bases: EnumCoercionMixin

A CurationTaskProperties for record-based metadata.

Represents a Synapse RecordBasedMetadataTaskProperties.

ATTRIBUTE DESCRIPTION
record_set_id

The synId of the RecordSet that will contain all record-based metadata

TYPE: Optional[str]

suggested_authorization_mode

Recommends who is allowed to access the curation grid session that a client opens for this task. The value is stored on the task as a suggestion; the client applies it when it creates a new session. Choose from SESSION_OWNER (only the person or team who owns the session can access it) or SOURCE_BENEFACTOR (anyone with EDIT permission on the data being curated can access the session). When omitted (None, the default), no recommendation is stored and clients fall back to their usual behavior.

TYPE: Optional[Union[AuthorizationMode, str]]

collaborator_principal_ids

Not actively used at this time. 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.

TYPE: Optional[list[str]]

Source code in synapseclient/models/curation.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
@dataclass
class RecordBasedMetadataTaskProperties(EnumCoercionMixin):
    """
    A CurationTaskProperties for record-based metadata.

    Represents a [Synapse RecordBasedMetadataTaskProperties](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/curation/metadata/RecordBasedMetadataTaskProperties.html).

    Attributes:
        record_set_id: The synId of the RecordSet that will contain all record-based metadata
        suggested_authorization_mode: Recommends who is allowed to access the curation
            grid session that a client opens for this task. The value is stored on the
            task as a suggestion; the client applies it when it creates a new session.
            Choose from SESSION_OWNER (only the person or team who owns the session can
            access it) or SOURCE_BENEFACTOR (anyone with EDIT permission on the data being
            curated can access the session). When omitted (None, the default), no
            recommendation is stored and clients fall back to their usual behavior.
        collaborator_principal_ids: Not actively used at this time. 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.
    """

    _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
    """Recommends who is allowed to access the curation
        grid session that a client opens for this task. The value is stored on the
        task as a suggestion; the client applies it when it creates a new session.
        Choose from:
        - SESSION_OWNER: only the person or team who owns the session can access it.
        - SOURCE_BENEFACTOR: anyone with EDIT permission on the
            data being curated can access the session. This lets editors collaborate
            in the same session without being added to a shared ownership team.
        When omitted (None, the default), no recommendation is stored and clients
        fall back to their usual behavior of finding or creating a private session
        for the current user. Changing this value after the task already exists
        resets the task's active session, so a new grid session must be opened
        before curation can continue."""

    collaborator_principal_ids: Optional[list[str]] = None
    """Not actively used at this time.
    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"""

    def fill_from_dict(
        self, synapse_response: Union[Dict[str, Any], Any]
    ) -> "RecordBasedMetadataTaskProperties":
        """
        Converts a response from the REST API into this dataclass.

        Arguments:
            synapse_response: The response from the REST API.

        Returns:
            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]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        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

Attributes

record_set_id class-attribute instance-attribute

record_set_id: Optional[str] = None

The synId of the RecordSet that will contain all record-based metadata

suggested_authorization_mode class-attribute instance-attribute

suggested_authorization_mode: Optional[Union[AuthorizationMode, str]] = None

Recommends who is allowed to access the curation grid session that a client opens for this task. The value is stored on the task as a suggestion; the client applies it when it creates a new session. Choose from: - SESSION_OWNER: only the person or team who owns the session can access it. - SOURCE_BENEFACTOR: anyone with EDIT permission on the data being curated can access the session. This lets editors collaborate in the same session without being added to a shared ownership team. When omitted (None, the default), no recommendation is stored and clients fall back to their usual behavior of finding or creating a private session for the current user. Changing this value after the task already exists resets the task's active session, so a new grid session must be opened before curation can continue.

collaborator_principal_ids class-attribute instance-attribute

collaborator_principal_ids: Optional[list[str]] = None

Not actively used at this time. 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


synapseclient.models.FileBasedMetadataTaskProperties dataclass

Bases: EnumCoercionMixin

A CurationTaskProperties for file-based data, describing where data is uploaded and a view which contains the annotations.

Represents a Synapse FileBasedMetadataTaskProperties.

ATTRIBUTE DESCRIPTION
upload_folder_id

The synId of the folder where data files of this type are to be uploaded

TYPE: Optional[str]

file_view_id

The synId of the FileView that shows all data of this type

TYPE: Optional[str]

suggested_authorization_mode

Recommends who is allowed to access the curation grid session that a client opens for this task. The value is stored on the task as a suggestion; the client applies it when it creates a new session. Choose from SESSION_OWNER (only the person or team who owns the session can access it) or SOURCE_BENEFACTOR (anyone with EDIT permission on the data being curated can access the session). When omitted (None, the default), no recommendation is stored and clients fall back to their usual behavior.

TYPE: Optional[Union[AuthorizationMode, str]]

collaborator_principal_ids

Not actively used at this time. 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.

TYPE: Optional[list[str]]

Source code in synapseclient/models/curation.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
@dataclass
class FileBasedMetadataTaskProperties(EnumCoercionMixin):
    """
    A CurationTaskProperties for file-based data, describing where data is uploaded
    and a view which contains the annotations.

    Represents a [Synapse FileBasedMetadataTaskProperties](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/curation/metadata/FileBasedMetadataTaskProperties.html).

    Attributes:
        upload_folder_id: The synId of the folder where data files of this type are to be uploaded
        file_view_id: The synId of the FileView that shows all data of this type
        suggested_authorization_mode: Recommends who is allowed to access the curation
            grid session that a client opens for this task. The value is stored on the
            task as a suggestion; the client applies it when it creates a new session.
            Choose from SESSION_OWNER (only the person or team who owns the session can
            access it) or SOURCE_BENEFACTOR (anyone with EDIT permission on the data being
            curated can access the session). When omitted (None, the default), no
            recommendation is stored and clients fall back to their usual behavior.
        collaborator_principal_ids: Not actively used at this time. 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.
    """

    _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
    """Recommends who is allowed to access the curation
        grid session that a client opens for this task. The value is stored on the
        task as a suggestion; the client applies it when it creates a new session.
        Choose from:
        - SESSION_OWNER: only the person or team who owns the session can access it.
        - SOURCE_BENEFACTOR: anyone with EDIT permission on the
            data being curated can access the session. This lets editors collaborate
            in the same session without being added to a shared ownership team.
        When omitted (None, the default), no recommendation is stored and clients
        fall back to their usual behavior of finding or creating a private session
        for the current user. Changing this value after the task already exists
        resets the task's active session, so a new grid session must be opened
        before curation can continue."""

    collaborator_principal_ids: Optional[list[str]] = None
    """Not actively used at this time.
    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"""

    def fill_from_dict(
        self, synapse_response: Union[Dict[str, Any], Any]
    ) -> "FileBasedMetadataTaskProperties":
        """
        Converts a response from the REST API into this dataclass.

        Arguments:
            synapse_response: The response from the REST API.

        Returns:
            The FileBasedMetadataTaskProperties object.
        """
        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]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        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

Attributes

upload_folder_id class-attribute instance-attribute

upload_folder_id: Optional[str] = None

The synId of the folder where data files of this type are to be uploaded

file_view_id class-attribute instance-attribute

file_view_id: Optional[str] = None

The synId of the FileView that shows all data of this type

suggested_authorization_mode class-attribute instance-attribute

suggested_authorization_mode: Optional[Union[AuthorizationMode, str]] = None

Recommends who is allowed to access the curation grid session that a client opens for this task. The value is stored on the task as a suggestion; the client applies it when it creates a new session. Choose from: - SESSION_OWNER: only the person or team who owns the session can access it. - SOURCE_BENEFACTOR: anyone with EDIT permission on the data being curated can access the session. This lets editors collaborate in the same session without being added to a shared ownership team. When omitted (None, the default), no recommendation is stored and clients fall back to their usual behavior of finding or creating a private session for the current user. Changing this value after the task already exists resets the task's active session, so a new grid session must be opened before curation can continue.

collaborator_principal_ids class-attribute instance-attribute

collaborator_principal_ids: Optional[list[str]] = None

Not actively used at this time. 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


synapseclient.models.AuthorizationMode

Bases: 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.

Source code in synapseclient/models/curation.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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"
    """Access is limited to the session owner or members of the owner's team. This is
    the default setting. When a view serves as the source, the owner can access all
    available rows, while other team members see data according to the owner's
    permission scope."""

    SOURCE_BENEFACTOR = "SOURCE_BENEFACTOR"
    """Access is granted to any user who has EDIT (UPDATE) access on all benefactor IDs
    captured when the session was created. This mode allows project administrators to
    enable collaborative grid access for all editors without maintaining a separate
    ownership team. User visibility of rows depends on their individual permissions."""

Attributes

SESSION_OWNER class-attribute instance-attribute

SESSION_OWNER = 'SESSION_OWNER'

Access is limited to the session owner or members of the owner's team. This is the default setting. When a view serves as the source, the owner can access all available rows, while other team members see data according to the owner's permission scope.

SOURCE_BENEFACTOR class-attribute instance-attribute

SOURCE_BENEFACTOR = 'SOURCE_BENEFACTOR'

Access is granted to any user who has EDIT (UPDATE) access on all benefactor IDs captured when the session was created. This mode allows project administrators to enable collaborative grid access for all editors without maintaining a separate ownership team. User visibility of rows depends on their individual permissions.


synapseclient.models.SyncType

Bases: ForwardCompatibleStrEnum

The type of synchronization to perform on a grid session.

See https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/SyncType.html.

Source code in synapseclient/models/curation.py
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
class SyncType(ForwardCompatibleStrEnum):
    """
    The type of synchronization to perform on a grid session.

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

    PULL = "PULL"
    """Update the grid with the latest data from the source, without writing the
    grid back to the source. Currently only supported for RecordSet-based grids."""

    PULL_PUSH = "PULL_PUSH"
    """Update the grid with the latest data from the source, then update the source
    (the referenced entities for EntityView-based grids, or the source RecordSet for
    RecordSet-based grids) with the grid data. This is the default when sync_type is
    not specified."""

Attributes

PULL class-attribute instance-attribute

PULL = 'PULL'

Update the grid with the latest data from the source, without writing the grid back to the source. Currently only supported for RecordSet-based grids.

PULL_PUSH class-attribute instance-attribute

PULL_PUSH = 'PULL_PUSH'

Update the grid with the latest data from the source, then update the source (the referenced entities for EntityView-based grids, or the source RecordSet for RecordSet-based grids) with the grid data. This is the default when sync_type is not specified.


synapseclient.models.Grid dataclass

Bases: EnumCoercionMixin, GridSynchronousProtocol

A GridSession provides functionality to create and manage grid sessions in Synapse. Grid sessions are used for curation workflows where data can be edited in a grid format and then exported back to record sets.

ATTRIBUTE DESCRIPTION
record_set_id

The synId of the RecordSet to use for initializing the grid

TYPE: Optional[str]

initial_query

Initialize a grid session from an EntityView. Mutually exclusive with record_set_id.

TYPE: Optional[Query]

owner_principal_id

The principal ID (user or team) that will own the created grid session. When not provided, the principal ID of the caller is used.

TYPE: int | None

authorization_mode

Controls access permissions and row visibility at session creation time. See AuthorizationMode. When not provided, the service default (SESSION_OWNER) is used.

TYPE: Optional[Union[AuthorizationMode, str]]

session_id

The unique sessionId that identifies the grid session

TYPE: Optional[str]

started_by

The user that started this session

TYPE: Optional[str]

started_on

The date-time when the session was started

TYPE: Optional[str]

etag

Changes when the session changes

TYPE: Optional[str]

modified_on

The date-time when the session was last changed

TYPE: Optional[str]

last_replica_id_client

The last replica ID issued to a client

TYPE: Optional[int]

last_replica_id_service

The last replica ID issued to a service

TYPE: Optional[int]

grid_json_schema_id

The $id of the JSON schema used for model validation

TYPE: Optional[str]

source_entity_id

The synId of the table/view/csv that this grid was cloned from

TYPE: Optional[str]

record_set_version_number

The version number of the exported record set

TYPE: Optional[int]

validation_summary_statistics

Summary statistics for validation results

TYPE: Optional[ValidationSummary]

Create and manage a grid session workflow

 

from synapseclient import Synapse
from synapseclient.models import Grid
from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll, SyncType

syn = Synapse()
syn.login()

# Create a new grid session from a record set
grid = Grid(record_set_id="syn1234567")
grid = grid.create()
print(f"Created grid session: {grid.session_id}")

# Validate rows. SelectAll() selects every column, so each row's full
# data is returned alongside its validation results.
with grid.connect() as session:
    query_request = QueryRequest(query=GridQuery(column_selection=[SelectAll()]))
    result = session.validate_rows(query_request=query_request)
    for row in result.rows:
        print(f"Row ID: {row.row_id}, Validation Result: {row.validation_results}")

# Later, push the modified data back to the record set
grid = grid.synchronize(sync_type=SyncType.PULL_PUSH)

# Clean up by deleting the session when done
grid.delete()
Working with grid sessions using queries

 

from synapseclient import Synapse
from synapseclient.models import Grid
from synapseclient.models.table_components import Query
from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll, SyncType

syn = Synapse()
syn.login()

# Create a grid from an entity view query
query = Query(sql="SELECT * FROM syn1234567")
grid = Grid(initial_query=query)
grid = grid.create()

# Validate rows. SelectAll() selects every column, so each row's full
# data is returned alongside its validation results.
with grid.connect() as session:
    query_request = QueryRequest(query=GridQuery(column_selection=[SelectAll()]))
    result = session.validate_rows(query_request=query_request)
    for row in result.rows:
        print(f"Row ID: {row.row_id}, Validation Result: {row.validation_results}")

# Push when ready
grid = grid.synchronize(sync_type=SyncType.PULL_PUSH)
Source code in synapseclient/models/curation.py
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
4647
4648
4649
4650
4651
4652
4653
4654
4655
4656
4657
4658
4659
4660
4661
4662
4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
4732
4733
4734
4735
4736
4737
4738
4739
4740
4741
4742
4743
4744
4745
4746
4747
4748
4749
4750
4751
4752
4753
4754
4755
4756
4757
4758
4759
4760
4761
4762
4763
4764
4765
4766
4767
4768
4769
4770
4771
4772
4773
4774
4775
4776
4777
4778
4779
4780
4781
4782
4783
4784
4785
4786
4787
4788
4789
4790
4791
4792
4793
4794
4795
4796
4797
4798
4799
4800
4801
4802
4803
4804
4805
4806
4807
4808
4809
4810
4811
4812
4813
4814
4815
4816
4817
4818
4819
4820
4821
4822
4823
4824
4825
4826
4827
4828
4829
4830
4831
4832
4833
4834
4835
4836
4837
4838
4839
4840
4841
4842
4843
4844
4845
4846
4847
4848
4849
4850
4851
4852
4853
4854
4855
4856
4857
4858
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
4872
4873
4874
4875
4876
4877
4878
4879
4880
4881
4882
4883
4884
4885
4886
4887
4888
4889
4890
4891
4892
4893
4894
4895
4896
4897
4898
4899
4900
4901
4902
4903
4904
4905
4906
4907
4908
4909
4910
4911
4912
4913
4914
4915
4916
4917
4918
4919
4920
4921
4922
4923
4924
4925
4926
4927
4928
4929
4930
4931
4932
4933
4934
4935
4936
4937
4938
4939
4940
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
4966
4967
4968
4969
4970
4971
4972
4973
4974
4975
4976
4977
4978
4979
4980
4981
4982
4983
4984
4985
4986
4987
4988
4989
4990
4991
4992
4993
4994
4995
4996
4997
4998
4999
5000
5001
5002
5003
5004
5005
5006
5007
5008
5009
5010
5011
5012
5013
5014
5015
5016
5017
5018
5019
5020
5021
5022
5023
5024
5025
5026
5027
5028
5029
5030
5031
5032
5033
5034
5035
5036
5037
5038
5039
5040
5041
5042
5043
5044
5045
5046
5047
5048
5049
5050
5051
5052
5053
5054
5055
5056
5057
5058
5059
5060
5061
5062
5063
5064
5065
5066
5067
5068
5069
5070
5071
5072
5073
5074
5075
5076
5077
5078
5079
5080
5081
5082
5083
5084
5085
5086
5087
5088
5089
5090
5091
5092
5093
5094
5095
5096
5097
5098
5099
5100
5101
5102
5103
5104
5105
5106
5107
5108
5109
5110
5111
5112
5113
5114
5115
5116
5117
5118
5119
5120
5121
5122
5123
5124
5125
5126
5127
5128
5129
5130
5131
5132
5133
5134
5135
5136
5137
5138
5139
5140
5141
5142
5143
5144
5145
5146
5147
5148
5149
5150
5151
5152
5153
5154
5155
5156
5157
5158
5159
5160
5161
5162
5163
5164
5165
5166
5167
5168
5169
5170
5171
5172
5173
5174
5175
5176
5177
5178
5179
5180
5181
5182
5183
5184
5185
5186
5187
5188
5189
5190
5191
5192
5193
5194
5195
5196
5197
5198
5199
5200
5201
5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
5215
5216
5217
5218
5219
5220
5221
5222
5223
5224
5225
5226
5227
5228
5229
5230
5231
5232
5233
5234
5235
5236
5237
5238
5239
5240
5241
5242
5243
5244
5245
5246
5247
5248
5249
5250
5251
5252
5253
5254
5255
5256
5257
5258
5259
5260
5261
5262
5263
5264
5265
5266
5267
5268
5269
5270
5271
5272
5273
5274
5275
5276
5277
5278
5279
5280
5281
5282
5283
5284
5285
5286
5287
5288
5289
5290
5291
5292
5293
5294
5295
5296
5297
5298
5299
5300
5301
5302
5303
5304
5305
5306
5307
5308
5309
5310
5311
5312
5313
5314
5315
5316
5317
5318
5319
5320
5321
5322
5323
5324
5325
5326
5327
5328
5329
5330
5331
5332
5333
5334
5335
5336
5337
5338
5339
5340
5341
5342
5343
5344
5345
5346
5347
5348
5349
5350
5351
5352
5353
5354
5355
5356
5357
5358
5359
5360
5361
5362
5363
5364
5365
5366
5367
5368
5369
5370
5371
5372
5373
5374
5375
5376
5377
5378
5379
5380
5381
5382
5383
5384
5385
5386
5387
5388
5389
5390
5391
5392
5393
5394
5395
5396
5397
5398
5399
5400
5401
5402
5403
5404
5405
5406
5407
5408
5409
5410
5411
5412
5413
5414
5415
5416
5417
5418
5419
5420
5421
5422
5423
5424
5425
5426
5427
5428
5429
5430
5431
5432
5433
5434
5435
5436
5437
5438
5439
5440
5441
5442
5443
5444
5445
5446
5447
5448
5449
5450
5451
5452
5453
5454
5455
5456
5457
5458
5459
5460
5461
5462
5463
5464
5465
5466
5467
5468
5469
5470
5471
5472
5473
5474
5475
5476
5477
5478
5479
5480
5481
5482
5483
5484
5485
5486
5487
5488
5489
5490
5491
5492
5493
5494
5495
5496
5497
5498
5499
5500
5501
5502
5503
5504
5505
5506
5507
5508
5509
5510
5511
5512
5513
5514
5515
5516
5517
5518
5519
5520
5521
5522
5523
5524
5525
5526
5527
5528
5529
5530
5531
5532
5533
5534
5535
5536
5537
5538
5539
5540
5541
5542
5543
5544
5545
5546
5547
5548
5549
5550
5551
5552
5553
5554
5555
5556
5557
5558
5559
5560
5561
5562
5563
5564
5565
5566
5567
5568
5569
5570
5571
5572
5573
5574
5575
5576
5577
5578
5579
5580
5581
5582
5583
5584
5585
5586
5587
5588
5589
5590
5591
5592
5593
5594
5595
5596
5597
5598
5599
5600
5601
5602
5603
5604
5605
5606
5607
5608
5609
5610
5611
5612
5613
5614
5615
5616
5617
5618
5619
5620
5621
5622
5623
5624
5625
5626
5627
5628
5629
5630
5631
5632
5633
5634
5635
5636
5637
5638
5639
5640
5641
5642
5643
5644
5645
5646
5647
5648
5649
5650
5651
5652
5653
5654
5655
5656
5657
5658
5659
5660
5661
5662
5663
5664
5665
5666
5667
5668
5669
5670
5671
5672
5673
5674
5675
5676
5677
5678
5679
5680
5681
5682
@dataclass
@async_to_sync
class Grid(EnumCoercionMixin, GridSynchronousProtocol):
    """
    A GridSession provides functionality to create and manage grid sessions in Synapse.
    Grid sessions are used for curation workflows where data can be edited in a grid format
    and then exported back to record sets.

    Attributes:
        record_set_id: The synId of the RecordSet to use for initializing the grid
        initial_query: Initialize a grid session from an EntityView.
            Mutually exclusive with record_set_id.
        owner_principal_id: The principal ID (user or team) that will own the
            created grid session. When not provided, the principal ID of the
            caller is used.
        authorization_mode: Controls access permissions and row visibility at
            session creation time. See AuthorizationMode. When not provided, the
            service default (SESSION_OWNER) is used.
        session_id: The unique sessionId that identifies the grid session
        started_by: The user that started this session
        started_on: The date-time when the session was started
        etag: Changes when the session changes
        modified_on: The date-time when the session was last changed
        last_replica_id_client: The last replica ID issued to a client
        last_replica_id_service: The last replica ID issued to a service
        grid_json_schema_id: The $id of the JSON schema used for model validation
        source_entity_id: The synId of the table/view/csv that this grid was cloned from
        record_set_version_number: The version number of the exported record set
        validation_summary_statistics: Summary statistics for validation results

    Example: Create and manage a grid session workflow
        &nbsp;

        ```python
        from synapseclient import Synapse
        from synapseclient.models import Grid
        from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll, SyncType

        syn = Synapse()
        syn.login()

        # Create a new grid session from a record set
        grid = Grid(record_set_id="syn1234567")
        grid = grid.create()
        print(f"Created grid session: {grid.session_id}")

        # Validate rows. SelectAll() selects every column, so each row's full
        # data is returned alongside its validation results.
        with grid.connect() as session:
            query_request = QueryRequest(query=GridQuery(column_selection=[SelectAll()]))
            result = session.validate_rows(query_request=query_request)
            for row in result.rows:
                print(f"Row ID: {row.row_id}, Validation Result: {row.validation_results}")

        # Later, push the modified data back to the record set
        grid = grid.synchronize(sync_type=SyncType.PULL_PUSH)

        # Clean up by deleting the session when done
        grid.delete()
        ```

    Example: Working with grid sessions using queries
        &nbsp;

        ```python
        from synapseclient import Synapse
        from synapseclient.models import Grid
        from synapseclient.models.table_components import Query
        from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll, SyncType

        syn = Synapse()
        syn.login()

        # Create a grid from an entity view query
        query = Query(sql="SELECT * FROM syn1234567")
        grid = Grid(initial_query=query)
        grid = grid.create()

        # Validate rows. SelectAll() selects every column, so each row's full
        # data is returned alongside its validation results.
        with grid.connect() as session:
            query_request = QueryRequest(query=GridQuery(column_selection=[SelectAll()]))
            result = session.validate_rows(query_request=query_request)
            for row in result.rows:
                print(f"Row ID: {row.row_id}, Validation Result: {row.validation_results}")

        # Push when ready
        grid = grid.synchronize(sync_type=SyncType.PULL_PUSH)
        ```
    """

    record_set_id: Optional[str] = None
    """The synId of the RecordSet to use for initializing the grid"""

    initial_query: Optional[Query] = None
    """Initialize a grid session from an EntityView.
    Mutually exclusive with record_set_id."""

    owner_principal_id: int | None = None
    """The principal ID (user or team) that will own the created grid session.
    When not provided, the principal ID of the caller is used."""

    authorization_mode: Optional[Union[AuthorizationMode, str]] = None
    """Controls access permissions and row visibility at session creation time.
    See AuthorizationMode. When not provided, the service default (SESSION_OWNER)
    is used."""

    session_id: Optional[str] = None
    """The unique sessionId that identifies the grid session"""

    started_by: Optional[str] = None
    """The user that started this session"""

    started_on: Optional[str] = None
    """The date-time when the session was started"""

    etag: Optional[str] = None
    """Changes when the session changes"""

    modified_on: Optional[str] = None
    """The date-time when the session was last changed"""

    last_replica_id_client: Optional[int] = None
    """The last replica ID issued to a client. Client replica IDs are incremented."""

    last_replica_id_service: Optional[int] = None
    """The last replica ID issued to a service. Service replica IDs are decremented."""

    grid_json_schema_id: Optional[str] = None
    """The $id of the JSON schema that will be used for model validation in this grid session"""

    source_entity_id: Optional[str] = None
    """The synId of the table/view/csv that this grid was cloned from"""

    record_set_version_number: Optional[int] = None
    """The version number of the exported record set"""

    validation_summary_statistics: Optional[ValidationSummary] = None
    """Summary statistics for validation results"""

    _replica_id: Optional[int] = field(default=None, repr=False, compare=False)
    """The replica ID bound to this instance by `connect_async`. Reused by
    `validate_rows_async` so repeated calls do not each create a new replica."""

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

    async def create_async(
        self,
        attach_to_previous_session=False,
        *,
        timeout: int = 120,
        synapse_client: Optional[Synapse] = None,
    ) -> "Grid":
        """
        Creates a new grid session from a `record_set_id` or `initial_query`.

        When using `record_set_id`, first checks for existing active sessions that match
        the record set before creating a new one. When using `initial_query`, always
        creates a new session due to the complexity of matching query parameters.

        Arguments:
            attach_to_previous_session: If True and using `record_set_id`, will attach
                to an existing active session if one exists. Defaults to False.
            timeout: The number of seconds to wait for the job to complete or progress
                before raising a SynapseTimeoutError. Defaults to 120.
            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.

        Returns:
            GridSession: The GridSession object with populated session_id.

        Raises:
            ValueError: If `record_set_id` or `initial_query` is not provided.

        Example: Create a grid session asynchronously
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid

            syn = Synapse()
            syn.login()

            async def main():
                # Create a grid session from a record set
                grid = Grid(record_set_id="syn1234567")
                grid = await grid.create_async()
                print(f"Created grid session: {grid.session_id}")

            asyncio.run(main())
            ```
        """
        if not self.record_set_id and not self.initial_query:
            raise ValueError(
                "record_set_id or initial_query is required to create a GridSession"
            )

        trace.get_current_span().set_attributes(
            {
                "synapse.record_set_id": self.record_set_id or "",
                "synapse.session_id": self.session_id or "",
            }
        )

        # Check for existing active sessions only when using record_set_id
        # For initial_query, always create a new session due to complexity of matching
        if self.record_set_id and attach_to_previous_session:
            # Look for existing active sessions for this record set
            async for existing_session in self.list_async(
                source_id=self.record_set_id, synapse_client=synapse_client
            ):
                # Found an existing session, populate this object with its data and return
                self.session_id = existing_session.session_id
                self.started_by = existing_session.started_by
                self.started_on = existing_session.started_on
                self.etag = existing_session.etag
                self.modified_on = existing_session.modified_on
                self.last_replica_id_client = existing_session.last_replica_id_client
                self.last_replica_id_service = existing_session.last_replica_id_service
                self.grid_json_schema_id = existing_session.grid_json_schema_id
                self.source_entity_id = existing_session.source_entity_id
                return self

        # No existing session found, create a new one
        create_request = CreateGridRequest(
            record_set_id=self.record_set_id,
            initial_query=self.initial_query,
            owner_principal_id=self.owner_principal_id,
            authorization_mode=self.authorization_mode,
        )
        result = await create_request.send_job_and_wait_async(
            timeout=timeout, synapse_client=synapse_client
        )

        # Fill this GridSession with the grid session data from the async job response
        result.fill_grid_session_from_response(self)

        return self

    @otel_trace_method(
        method_to_trace_name=lambda self, **kwargs: f"Grid_Get: ID: {self.session_id}"
    )
    async def get_async(self, *, synapse_client: Optional[Synapse] = None) -> "Grid":
        """
        Get a grid session from Synapse by its session_id.

        Arguments:
            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.

        Returns:
            The Grid object populated with the session data from Synapse.

        Raises:
            ValueError: If session_id is not provided.

        Example: Get a grid session by its session id asynchronously
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid

            syn = Synapse()
            syn.login()

            async def main():
                grid = await Grid(session_id="abc-123-def").get_async()
                print(f"Source entity: {grid.source_entity_id}")

            asyncio.run(main())
            ```
        """
        if not self.session_id:
            raise ValueError("session_id is required to get a GridSession")

        response = await get_grid_session(
            session_id=self.session_id, synapse_client=synapse_client
        )
        self.fill_from_dict(response)
        return self

    @deprecated(
        version="4.14.0",
        reason="Use `synchronize_async` with `sync_type=SyncType.PULL_PUSH` instead.",
    )
    async def export_to_record_set_async(
        self, *, timeout: int = 120, synapse_client: Optional[Synapse] = None
    ) -> "Grid":
        """
        Exports the grid session data back to a record set. This will create a new version
        of the original record set with the modified data from the grid session.

        WARNING - This method is deprecated and will be removed in a future release.
        Use `synchronize`/`synchronize_async` with `sync_type=SyncType.PULL_PUSH` instead.

        Arguments:
            timeout: The number of seconds to wait for the job to complete or progress
                before raising a SynapseTimeoutError. Defaults to 120.
            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.

        Returns:
            GridSession: The GridSession object with export information populated.

        Raises:
            ValueError: If session_id is not provided.

        Example: Migration to synchronize_async
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid
            from synapseclient.models.curation import SyncType

            syn = Synapse()
            syn.login()

            async def main():
                grid = Grid(session_id="abc-123-def")

                # Old approach (DEPRECATED)
                # grid = await grid.export_to_record_set_async()
                # print(f"Exported to record set: {grid.record_set_id}")
                # print(f"Version number: {grid.record_set_version_number}")
                # if grid.validation_summary_statistics:
                #     print(f"Valid records: {grid.validation_summary_statistics.number_of_valid_children}")

                # New approach (RECOMMENDED)
                # Note: unlike export_to_record_set_async, synchronize_async
                # does not populate record_set_id, record_set_version_number,
                # or validation_summary_statistics on the returned Grid.
                grid = await grid.synchronize_async(sync_type=SyncType.PULL_PUSH)

            asyncio.run(main())
            ```
        """
        if not self.session_id:
            raise ValueError("session_id is required to export a GridSession")

        trace.get_current_span().set_attributes(
            {
                "synapse.session_id": self.session_id or "",
            }
        )

        # Create and send the export request
        export_request = GridRecordSetExportRequest(session_id=self.session_id)
        result = await export_request.send_job_and_wait_async(
            timeout=timeout, synapse_client=synapse_client
        )

        self.record_set_id = result.response_record_set_id
        self.record_set_version_number = result.record_set_version_number
        self.validation_summary_statistics = result.validation_summary_statistics

        return self

    def fill_from_dict(self, synapse_response: Dict[str, Any]) -> "Grid":
        """Converts a response from the REST API into this dataclass."""
        self.session_id = synapse_response.get("sessionId", None)
        self.started_by = synapse_response.get("startedBy", None)
        self.started_on = synapse_response.get("startedOn", None)
        self.etag = synapse_response.get("etag", None)
        self.modified_on = synapse_response.get("modifiedOn", None)
        self.last_replica_id_client = synapse_response.get("lastReplicaIdClient", None)
        self.last_replica_id_service = synapse_response.get(
            "lastReplicaIdService", None
        )
        self.grid_json_schema_id = synapse_response.get("gridJsonSchema$Id", None)
        self.source_entity_id = synapse_response.get("sourceEntityId", None)
        owner_principal_id = synapse_response.get("ownerPrincipalId")
        self.owner_principal_id = (
            int(owner_principal_id) if owner_principal_id is not None else None
        )
        self.authorization_mode = synapse_response.get("authorizationMode", None)
        return self

    @skip_async_to_sync
    @classmethod
    async def list_async(
        cls,
        source_id: Optional[str] = None,
        *,
        synapse_client: Optional[Synapse] = None,
    ) -> AsyncGenerator["Grid", None]:
        """
        Generator to get a list of active grid sessions for the user.

        Arguments:
            source_id: Optional. When provided, only sessions with this synId will be returned.
            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.

        Yields:
            Grid objects representing active grid sessions.

        Example: List all active grid sessions asynchronously
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid

            syn = Synapse()
            syn.login()

            async def main():
                # List all active grid sessions for the user
                async for grid in Grid.list_async():
                    print(f"Session ID: {grid.session_id}")
                    print(f"Source Entity: {grid.source_entity_id}")
                    print(f"Started: {grid.started_on}")
                    print("---")

                # List grid sessions for a specific source
                async for grid in Grid.list_async(source_id="syn1234567"):
                    print(f"Session ID: {grid.session_id}")
                    print(f"Modified: {grid.modified_on}")

            asyncio.run(main())
            ```
        """
        async for session_dict in list_grid_sessions(
            source_id=source_id, synapse_client=synapse_client
        ):
            # Convert the dictionary to a Grid object
            grid = cls()
            grid.fill_from_dict(session_dict)
            yield grid

    @classmethod
    def list(
        cls,
        source_id: Optional[str] = None,
        *,
        synapse_client: Optional[Synapse] = None,
    ) -> Generator["Grid", None, None]:
        """
        Generator to get a list of active grid sessions for the user.

        Arguments:
            source_id: Optional. When provided, only sessions with this synId will be returned.
            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.

        Yields:
            Grid objects representing active grid sessions.
        """
        return wrap_async_generator_to_sync_generator(
            async_gen_func=cls.list_async,
            source_id=source_id,
            synapse_client=synapse_client,
        )

    @otel_trace_method(
        method_to_trace_name=lambda self, **kwargs: f"Grid_Delete: ID: {self.session_id}"
    )
    async def delete_async(self, *, synapse_client: Optional[Synapse] = None) -> None:
        """
        Delete the grid session.

        Note: Only the user that created a grid session may delete it.

        Arguments:
            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.

        Returns:
            None

        Raises:
            ValueError: If session_id is not provided.

        Example: Delete a grid session asynchronously
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid

            syn = Synapse()
            syn.login()

            async def main():
                # Delete the grid session
                grid = Grid(session_id="abc-123-def")
                await grid.delete_async()
                print("Grid session deleted successfully")

            asyncio.run(main())
            ```
        """
        if not self.session_id:
            raise ValueError("session_id is required to delete a GridSession")

        trace.get_current_span().set_attributes(
            {
                "synapse.session_id": self.session_id or "",
            }
        )

        await delete_grid_session(
            session_id=self.session_id, synapse_client=synapse_client
        )

    @otel_trace_method(
        method_to_trace_name=lambda self, **kwargs: f"Grid_ImportCsv: ID: {self.session_id}"
    )
    async def import_csv_async(
        self,
        path: str,
        *,
        timeout: int = 120,
        csv_table_descriptor: Optional[CsvTableDescriptor] = None,
        synapse_client: Optional[Synapse] = None,
    ) -> "Grid":
        """
        Import a CSV file into this grid session. Previews the file to determine
        the column schema, then imports the data. Currently supports only grids
        created from a record set.

        Arguments:
            path: Local path to the CSV file to import.
            csv_table_descriptor: The description of the CSV format (delimiter,
                quote character, etc.). If not provided, the default CSV format
                will be used.
            timeout: The number of seconds to wait for each async job to complete
                or progress before raising a SynapseTimeoutError. Defaults to 120.
            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.

        Returns:
            The Grid object.

        Raises:
            ValueError: If session_id is not provided.

        Example: Import a CSV file into a grid session asynchronously
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid

            syn = Synapse()
            syn.login()

            async def main():
                grid = Grid(session_id="abc-123-def")
                grid = await grid.import_csv_async(path="/local/path/to/data.csv")
                print(f"Import complete for session: {grid.session_id}")

            asyncio.run(main())
            ```
        """

        if not self.session_id:
            raise ValueError(
                "session_id is required to import a CSV into a GridSession"
            )

        if not os.path.isfile(path):
            raise ValueError(f"Path '{path}' is not a valid file.")

        trace.get_current_span().set_attributes(
            {
                "synapse.session_id": self.session_id,
            }
        )

        client = Synapse.get_client(synapse_client=synapse_client)
        file_handle = await upload_synapse_s3(syn=client, file_path=path)
        file_handle_id = file_handle["id"]

        effective_descriptor = csv_table_descriptor or CsvTableDescriptor()

        upload_to_table_preview = UploadToTablePreviewRequest(
            csv_table_descriptor=effective_descriptor,
            upload_file_handle_id=file_handle_id,
        )

        preview_response = await upload_to_table_preview.send_job_and_wait_async(
            timeout=timeout, synapse_client=synapse_client
        )
        if not preview_response.suggested_columns:
            raise ValueError(
                f"CSV preview for file handle {file_handle_id} returned no suggested "
                f"columns (rows scanned: {preview_response.rows_scanned}). The file may "
                f"be empty, contain only a header row, or use a separator different "
                f"from the configured csv_table_descriptor "
                f"(separator={repr(effective_descriptor.separator)})."
            )

        import_request = GridCsvImportRequest(
            session_id=self.session_id,
            file_handle_id=file_handle_id,
            schema=preview_response.suggested_columns,
            csv_descriptor=effective_descriptor,
        )
        import_response = await import_request.send_job_and_wait_async(
            timeout=timeout, synapse_client=synapse_client
        )
        client.logger.info(
            f"CSV import to grid session {self.session_id} completed successfully, "
            f"total count: {import_response.total_count}, "
            f"total created: {import_response.created_count}, "
            f"total updated: {import_response.updated_count}"
        )

        return self

    @otel_trace_method(
        method_to_trace_name=lambda self, *args, **kwargs: f"Grid_DownloadCsv: ID: {self.session_id}"
    )
    async def download_csv_async(
        self,
        *,
        destination: Optional[str] = None,
        write_header: bool = True,
        include_row_id_and_row_version: bool = False,
        include_etag: bool = False,
        csv_table_descriptor: Optional[CsvTableDescriptor] = None,
        file_name: Optional[str] = None,
        timeout: int = 120,
        synapse_client: Optional[Synapse] = None,
    ) -> str:
        """
        Asynchronously download the current state of this grid session as a CSV file.

        Submits a DownloadFromGridRequest async job, waits for it to complete,
        then downloads the resulting CSV to the local filesystem.

        Arguments:
            destination: Local directory path where the CSV will be saved. The directory must already exist.
                If not provided, defaults to the current working directory.
            write_header: Whether the first line should contain column names
                as a header. Defaults to True.
            include_row_id_and_row_version: Whether the first two columns
                should contain row ID and version. Defaults to False.
            include_etag: Whether a column should contain the row etag.
                Defaults to False.
            csv_table_descriptor: The description of the CSV format (delimiter,
                quote character, etc.). If not provided, the default CSV format
                will be used.
            file_name: The optional name for the downloaded file. If not
                provided, defaults to `grid_{session_id}-{timestamp}.csv`.
            timeout: The number of seconds to wait for the async job to
                complete or progress before raising a SynapseTimeoutError.
                Defaults to 120.
            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.

        Returns:
            The local path to the downloaded CSV file.

        Raises:
            ValueError: If session_id is not provided.

        Example: Download a grid session as a CSV asynchronously
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid

            syn = Synapse()
            syn.login()

            async def main():
                grid = Grid(session_id="abc-123-def")
                path = await grid.download_csv_async(destination="./downloads")
                print(f"Downloaded CSV to: {path}")

            asyncio.run(main())
            ```

        Example: Download a grid session as a CSV with a custom file name asynchronously
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid

            syn = Synapse()
            syn.login()

            async def main():
                grid = Grid(session_id="abc-123-def")
                path = await grid.download_csv_async(
                    destination="./downloads", file_name="my_export.csv"
                )
                print(f"Downloaded CSV to: {path}")

            asyncio.run(main())
            ```
        """
        if not self.session_id:
            raise ValueError("session_id is required to download a GridSession as CSV")

        if not destination:
            destination = os.getcwd()

        if not os.path.isdir(destination):
            raise ValueError(f"Destination {destination} is not a valid directory.")

        trace.get_current_span().set_attributes({"synapse.session_id": self.session_id})

        effective_descriptor = csv_table_descriptor or CsvTableDescriptor()
        request = DownloadFromGridRequest(
            session_id=self.session_id,
            write_header=write_header,
            include_row_id_and_row_version=include_row_id_and_row_version,
            include_etag=include_etag,
            csv_table_descriptor=effective_descriptor,
            file_name=file_name,
        )
        download_response = await request.send_job_and_wait_async(
            timeout=timeout, synapse_client=synapse_client
        )
        if not download_response.results_file_handle_id:
            raise ValueError(
                f"Download job for grid session '{self.session_id}' completed but "
                "did not return a file handle ID. The CSV result may be empty or "
                "the job may have failed silently."
            )
        file_handle, presigned_url = await asyncio.gather(
            get_file_handle(
                file_handle_id=download_response.results_file_handle_id,
                synapse_client=synapse_client,
            ),
            get_file_handle_presigned_url(
                file_handle_id=download_response.results_file_handle_id,
                synapse_client=synapse_client,
            ),
        )
        if not file_name:
            timestamp = datetime.now(tz=timezone.utc).strftime("%Y%m%d%H%M%S")
            file_name = f"grid_{self.session_id}-{timestamp}.csv"
        file_path = os.path.join(destination, file_name)
        return await asyncio.to_thread(
            download_from_url,
            url=presigned_url,
            destination=file_path,
            file_handle_id=file_handle["id"],
            expected_md5=file_handle.get("contentMd5"),
            url_is_presigned=True,
            synapse_client=synapse_client,
        )

    @otel_trace_method(
        method_to_trace_name=lambda self, **kwargs: f"Grid_Synchronize: ID: {self.session_id}"
    )
    async def synchronize_async(
        self,
        *,
        sync_type: Optional[Union[SyncType, str]] = None,
        timeout: int = 120,
        synapse_client: Optional[Synapse] = None,
    ) -> "Grid":
        """
        Synchronizes the grid session's schema and row data against its source entity.

        Grid sessions created from a file view via `initial_query` always perform a
        full PULL_PUSH. Grid sessions backed by a RecordSet may instead pass
        `sync_type=SyncType.PULL` to pull the latest RecordSet data/schema into the
        session for review, without immediately writing the merged result back as a
        new RecordSet version. Once satisfied with the result, call this method again
        (with `sync_type` omitted, or explicitly set to `SyncType.PULL_PUSH`) to push
        the merged data back to the RecordSet as a new version.

        Arguments:
            sync_type: The type of synchronization to perform. Optional; the server
                defaults to `SyncType.PULL_PUSH` when omitted. `SyncType.PULL` is
                currently only supported for RecordSet-based grids.
            timeout: The number of seconds to wait for the job to complete or progress
                before raising a SynapseTimeoutError. Defaults to 120.
            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.

        Returns:
            Grid: The Grid object.

        Raises:
            ValueError: If session_id is not provided.

        Example: Synchronize a grid session created from a file view
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid
            from synapseclient.models.table_components import Query

            syn = Synapse()
            syn.login()

            async def main():
                # First create a grid session from a file view
                query = Query(sql="SELECT * FROM syn1234567")
                grid = Grid(initial_query=query)
                grid = await grid.create_async()

                # Synchronize the grid with the latest state of the file view
                grid = await grid.synchronize_async()

            asyncio.run(main())
            ```

        Example: Preview a RecordSet-backed grid's merge before pushing it back
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid
            from synapseclient.models.curation import SyncType

            syn = Synapse()
            syn.login()

            async def main():
                grid = Grid(record_set_id="syn1234567")
                grid = await grid.create_async()

                # Pull in the latest RecordSet data/schema without pushing back yet
                grid = await grid.synchronize_async(sync_type=SyncType.PULL)

                # ... review the merged result in the grid session ...

                # Push the merged result back as a new RecordSet version
                grid = await grid.synchronize_async(sync_type=SyncType.PULL_PUSH)

            asyncio.run(main())
            ```
        """
        if not self.session_id:
            raise ValueError("session_id is required to synchronize a GridSession")

        request = SynchronizeGridRequest(
            grid_session_id=self.session_id, sync_type=sync_type
        )
        result = await request.send_job_and_wait_async(
            timeout=timeout, synapse_client=synapse_client
        )

        if result.error_messages:
            client = Synapse.get_client(synapse_client=synapse_client)
            client.logger.error(
                f"Grid session '{self.session_id}' synchronization completed with "
                f"error messages: {result.error_messages}"
            )

        return self

    @otel_trace_method(
        method_to_trace_name=lambda self, **kwargs: f"Grid_Create_Replica_Session_ID: {self.session_id}"
    )
    async def _create_replica_async(
        self, *, synapse_client: Optional[Synapse] = None
    ) -> "GridReplica":
        """
        Creates a new replica for this grid session.

        A grid replica is an in-memory document that represents a 'copy' of the
        grid. Each replica is identified by a unique replicaId, issued by the
        'hub'. A user can have more than one replica at a time (i.e. using
        multiple browser tabs/machines). Only the user that started the grid session
        may create a replica.

        Arguments:
            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.

        Returns:
            The newly created GridReplica.

        Raises:
            ValueError: If session_id is not provided, or if the Synapse response
                did not contain replica information.

        Example: Create a replica for a grid session
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid

            syn = Synapse()
            syn.login()

            async def main():
                grid = Grid(record_set_id="syn1234567")
                grid = await grid.create_async()

                replica = await grid._create_replica_async()
                print(f"Replica created with ID: {replica.replica_id}")

            asyncio.run(main())
            ```
        """
        if not self.session_id:
            raise ValueError(
                "session_id is required to create a replica for a GridSession"
            )

        grid_replica = await create_grid_replica(
            session_id=self.session_id,
            create_replica_request=CreateReplicaRequest(
                self.session_id
            ).to_synapse_request(),
            synapse_client=synapse_client,
        )
        replica_data = grid_replica.get("replica")
        if not replica_data:
            raise ValueError(
                f"Replica could not be created for grid session '{self.session_id}': "
                f"no replica was returned in the Synapse response."
            )
        return GridReplica().fill_from_dict(replica_data)

    @skip_async_to_sync
    @asynccontextmanager
    async def connect_async(
        self,
        *,
        attach_to_previous_session: bool = False,
        timeout: int = 120,
        synapse_client: Optional[Synapse] = None,
    ) -> AsyncGenerator["Grid", None]:
        """
        Connects to a grid session and binds a single replica to it for the
        duration of the `async with` block.

        If `session_id` is not already set (e.g. from `create_grid_session`),
        creates a new grid session first via `record_set_id` or
        `initial_query`, same as `create_async`. Either way, one replica is
        then created (see `_create_replica_async`) that is reused by every
        `validate_rows_async` call made within the block.

        Arguments:
            attach_to_previous_session: Only applies when creating a new
                session from `record_set_id`. If True, attaches to an existing
                active session instead of creating a new one. Defaults to False.
            timeout: The number of seconds to wait for the job to complete or progress
                before raising a SynapseTimeoutError. Defaults to 120.
            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.

        Yields:
            The connected Grid, with a replica bound to it.

        Example: Validate rows using a newly created grid session
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid
            from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll

            syn = Synapse()
            syn.login()

            async def main():
                async with Grid(record_set_id="syn1234567").connect_async() as session:
                    query_request = QueryRequest(
                        query=GridQuery(column_selection=[SelectAll()])
                    )
                    result = await session.validate_rows_async(query_request=query_request)
                    for row in result.rows:
                        print(f"Row ID: {row.row_id}, Validation Result: {row.validation_results}")

            asyncio.run(main())
            ```

        Example: Validate rows using an existing grid session
            &nbsp;

            If a session_id is already set, connect_async will not create a new
            grid session

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import CurationTask, Grid
            from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll

            syn = Synapse()
            syn.login()

            task = CurationTask(task_id="1234")
            grid = task.create_grid_session()

            async def main():
                async with grid.connect_async() as session:
                    query_request = QueryRequest(
                        query=GridQuery(column_selection=[SelectAll()])
                    )
                    result = await session.validate_rows_async(query_request=query_request)
                    for row in result.rows:
                        print(f"Row ID: {row.row_id}, Validation Result: {row.validation_results}")

            asyncio.run(main())
            ```
        """
        trace.get_current_span().set_attributes(
            {
                "synapse.record_set_id": self.record_set_id or "",
                "synapse.session_id": self.session_id or "",
            }
        )

        if not self.session_id:
            await self.create_async(
                attach_to_previous_session=attach_to_previous_session,
                timeout=timeout,
                synapse_client=synapse_client,
            )

        replica = await self._create_replica_async(synapse_client=synapse_client)
        self._replica_id = replica.replica_id
        try:
            yield self
        finally:
            self._replica_id = None

    @contextmanager
    def connect(
        self,
        *,
        attach_to_previous_session: bool = False,
        timeout: int = 120,
        synapse_client: Optional[Synapse] = None,
    ) -> Generator["Grid", None, None]:
        """
        Synchronous equivalent of `connect_async`.

        Connects to a grid session and binds a single replica to it for the
        duration of the `with` block.

        If `session_id` is not already set (e.g. from `create_grid_session`),
        creates a new grid session first via `record_set_id` or
        `initial_query`, same as `create`. Either way, one replica is then
        created (see `_create_replica`) that is reused by every
        `validate_rows` call made within the block.

        Arguments:
            attach_to_previous_session: Only applies when creating a new
                session from `record_set_id`. If True, attaches to an existing
                active session instead of creating a new one. Defaults to False.
            timeout: The number of seconds to wait for the job to complete or progress
                before raising a SynapseTimeoutError. Defaults to 120.
            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.

        Yields:
            The connected Grid, with a replica bound to it.

        Example: Validate rows using a newly created grid session
            &nbsp;

            ```python
            from synapseclient import Synapse
            from synapseclient.models import Grid
            from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll

            syn = Synapse()
            syn.login()

            with Grid(record_set_id="syn1234567").connect() as session:
                query_request = QueryRequest(
                    query=GridQuery(column_selection=[SelectAll()])
                )
                result = session.validate_rows(query_request=query_request)
                for row in result.rows:
                    print(f"Row ID: {row.row_id}, Validation Result: {row.validation_results}")
            ```

        Example: Validate rows using an existing grid session
            &nbsp;

            If a session_id is already set, connect will not create a new grid
            session.

            ```python
            from synapseclient import Synapse
            from synapseclient.models import CurationTask, Grid
            from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll

            syn = Synapse()
            syn.login()

            task = CurationTask(task_id="1234")
            grid = task.create_grid_session()

            with grid.connect() as session:
                query_request = QueryRequest(
                    query=GridQuery(column_selection=[SelectAll()])
                )
                result = session.validate_rows(query_request=query_request)
                for row in result.rows:
                    print(f"Row ID: {row.row_id}, Validation Result: {row.validation_results}")
            ```
        """
        trace.get_current_span().set_attributes(
            {
                "synapse.record_set_id": self.record_set_id or "",
                "synapse.session_id": self.session_id or "",
            }
        )

        if not self.session_id:
            self.create(
                attach_to_previous_session=attach_to_previous_session,
                timeout=timeout,
                synapse_client=synapse_client,
            )

        replica = self._create_replica(synapse_client=synapse_client)
        self._replica_id = replica.replica_id
        try:
            yield self
        finally:
            self._replica_id = None

    @otel_trace_method(
        method_to_trace_name=lambda self, **kwargs: f"Grid_Validate_Rows_Session_ID: {self.session_id}"
    )
    async def validate_rows_async(
        self,
        *,
        timeout: int = 120,
        query_request: QueryRequest,
        synapse_client: Optional[Synapse] = None,
    ) -> Optional["GridQueryResult"]:
        """
        Queries this grid session's rows and returns their per-row validation
        results against the grid's bound JSON schema.

        This Grid must have been obtained from `connect_async` (or `connect`),
        which binds a replica to it. The given query_request is then submitted
        to the grid session using that replica, and this waits for the job to
        complete.

        Arguments:
            timeout: The number of seconds to wait for the job to complete or progress
                before raising a SynapseTimeoutError. Defaults to 120.
            query_request: The structured query to run against the grid, wrapping a
                GridQuery that defines the column selection, filters, and whether to
                include the detailed `all_validation_messages` list on each row.
            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.

        Returns:
            The GridQueryResult containing the selected columns and rows, each with its own validation_results, or None if the completed job did not return a query_result. Logs a warning if the job completed but no rows matched the query.

        Raises:
            ValueError: If session_id is not provided, or if no replica is bound
                to this Grid (see `connect_async`/`connect`).

        Example: Validate every row of a grid session
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid
            from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll

            syn = Synapse()
            syn.login()

            async def main():
                async with Grid(record_set_id="syn1234567").connect_async() as grid:
                    # SelectAll() selects every column in the grid, so each row's
                    # full data is returned alongside its validation results.
                    query_request = QueryRequest(
                        query=GridQuery(column_selection=[SelectAll()])
                    )
                    query_result = await grid.validate_rows_async(query_request=query_request)

                    for row in query_result.rows:
                        validation = row.validation_results
                        print(f"Row ID: {row.row_id}, Validation Result: {validation}")

            asyncio.run(main())
            ```

        Example: Validate only the currently invalid rows of a grid session
            &nbsp;

            ```python
            import asyncio
            from synapseclient import Synapse
            from synapseclient.models import Grid
            from synapseclient.models.curation import (
                GridQuery,
                QueryRequest,
                RowIsValidFilter,
                SelectAll,
            )

            syn = Synapse()
            syn.login()

            async def main():
                async with Grid(record_set_id="syn1234567").connect_async() as grid:
                    # Filter to only the rows that are currently invalid, and request
                    # the detailed allValidationMessages list on each one.
                    query_request = QueryRequest(
                        query=GridQuery(
                            column_selection=[SelectAll()],
                            filters=[RowIsValidFilter(value=False)],
                            include_validation_messages=True,
                        )
                    )
                    query_result = await grid.validate_rows_async(query_request=query_request)

                    for row in query_result.rows:
                        print(f"Invalid row {row.row_id}: {row.validation_results}")

            asyncio.run(main())
            ```
        """
        if not self.session_id:
            raise ValueError("session_id is required to validate rows")

        if self._replica_id is None:
            raise ValueError(
                "No replica is bound to this Grid. Use `connect_async` (or "
                "`connect`) to connect to a grid session before calling "
                "validate_rows_async."
            )

        request = GridQueryJobRequest(
            session_id=self.session_id,
            replica_id=self._replica_id,
            query_request=query_request,
        )
        request = await request.send_job_and_wait_async(
            timeout=timeout, synapse_client=synapse_client
        )

        if not request.query_result or not request.query_result.rows:
            client = Synapse.get_client(synapse_client=synapse_client)
            client.logger.warning(
                f"Validation job for grid session '{self.session_id}' completed but "
                "did not return any row validation results. This grid may not have "
                "any rows matching the query."
            )
        return request.query_result

Methods:

create_async async

create_async(attach_to_previous_session=False, *, timeout: int = 120, synapse_client: Optional[Synapse] = None) -> Grid

Creates a new grid session from a record_set_id or initial_query.

When using record_set_id, first checks for existing active sessions that match the record set before creating a new one. When using initial_query, always creates a new session due to the complexity of matching query parameters.

PARAMETER DESCRIPTION
attach_to_previous_session

If True and using record_set_id, will attach to an existing active session if one exists. Defaults to False.

DEFAULT: False

timeout

The number of seconds to wait for the job to complete or progress before raising a SynapseTimeoutError. Defaults to 120.

TYPE: int DEFAULT: 120

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
GridSession

The GridSession object with populated session_id.

TYPE: Grid

RAISES DESCRIPTION
ValueError

If record_set_id or initial_query is not provided.

Create a grid session asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid

syn = Synapse()
syn.login()

async def main():
    # Create a grid session from a record set
    grid = Grid(record_set_id="syn1234567")
    grid = await grid.create_async()
    print(f"Created grid session: {grid.session_id}")

asyncio.run(main())
Source code in synapseclient/models/curation.py
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
async def create_async(
    self,
    attach_to_previous_session=False,
    *,
    timeout: int = 120,
    synapse_client: Optional[Synapse] = None,
) -> "Grid":
    """
    Creates a new grid session from a `record_set_id` or `initial_query`.

    When using `record_set_id`, first checks for existing active sessions that match
    the record set before creating a new one. When using `initial_query`, always
    creates a new session due to the complexity of matching query parameters.

    Arguments:
        attach_to_previous_session: If True and using `record_set_id`, will attach
            to an existing active session if one exists. Defaults to False.
        timeout: The number of seconds to wait for the job to complete or progress
            before raising a SynapseTimeoutError. Defaults to 120.
        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.

    Returns:
        GridSession: The GridSession object with populated session_id.

    Raises:
        ValueError: If `record_set_id` or `initial_query` is not provided.

    Example: Create a grid session asynchronously
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid

        syn = Synapse()
        syn.login()

        async def main():
            # Create a grid session from a record set
            grid = Grid(record_set_id="syn1234567")
            grid = await grid.create_async()
            print(f"Created grid session: {grid.session_id}")

        asyncio.run(main())
        ```
    """
    if not self.record_set_id and not self.initial_query:
        raise ValueError(
            "record_set_id or initial_query is required to create a GridSession"
        )

    trace.get_current_span().set_attributes(
        {
            "synapse.record_set_id": self.record_set_id or "",
            "synapse.session_id": self.session_id or "",
        }
    )

    # Check for existing active sessions only when using record_set_id
    # For initial_query, always create a new session due to complexity of matching
    if self.record_set_id and attach_to_previous_session:
        # Look for existing active sessions for this record set
        async for existing_session in self.list_async(
            source_id=self.record_set_id, synapse_client=synapse_client
        ):
            # Found an existing session, populate this object with its data and return
            self.session_id = existing_session.session_id
            self.started_by = existing_session.started_by
            self.started_on = existing_session.started_on
            self.etag = existing_session.etag
            self.modified_on = existing_session.modified_on
            self.last_replica_id_client = existing_session.last_replica_id_client
            self.last_replica_id_service = existing_session.last_replica_id_service
            self.grid_json_schema_id = existing_session.grid_json_schema_id
            self.source_entity_id = existing_session.source_entity_id
            return self

    # No existing session found, create a new one
    create_request = CreateGridRequest(
        record_set_id=self.record_set_id,
        initial_query=self.initial_query,
        owner_principal_id=self.owner_principal_id,
        authorization_mode=self.authorization_mode,
    )
    result = await create_request.send_job_and_wait_async(
        timeout=timeout, synapse_client=synapse_client
    )

    # Fill this GridSession with the grid session data from the async job response
    result.fill_grid_session_from_response(self)

    return self

get_async async

get_async(*, synapse_client: Optional[Synapse] = None) -> Grid

Get a grid session from Synapse by its session_id.

PARAMETER DESCRIPTION
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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
Grid

The Grid object populated with the session data from Synapse.

RAISES DESCRIPTION
ValueError

If session_id is not provided.

Get a grid session by its session id asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid

syn = Synapse()
syn.login()

async def main():
    grid = await Grid(session_id="abc-123-def").get_async()
    print(f"Source entity: {grid.source_entity_id}")

asyncio.run(main())
Source code in synapseclient/models/curation.py
4648
4649
4650
4651
4652
4653
4654
4655
4656
4657
4658
4659
4660
4661
4662
4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
@otel_trace_method(
    method_to_trace_name=lambda self, **kwargs: f"Grid_Get: ID: {self.session_id}"
)
async def get_async(self, *, synapse_client: Optional[Synapse] = None) -> "Grid":
    """
    Get a grid session from Synapse by its session_id.

    Arguments:
        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.

    Returns:
        The Grid object populated with the session data from Synapse.

    Raises:
        ValueError: If session_id is not provided.

    Example: Get a grid session by its session id asynchronously
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid

        syn = Synapse()
        syn.login()

        async def main():
            grid = await Grid(session_id="abc-123-def").get_async()
            print(f"Source entity: {grid.source_entity_id}")

        asyncio.run(main())
        ```
    """
    if not self.session_id:
        raise ValueError("session_id is required to get a GridSession")

    response = await get_grid_session(
        session_id=self.session_id, synapse_client=synapse_client
    )
    self.fill_from_dict(response)
    return self

export_to_record_set_async async

export_to_record_set_async(*, timeout: int = 120, synapse_client: Optional[Synapse] = None) -> Grid

Exports the grid session data back to a record set. This will create a new version of the original record set with the modified data from the grid session.

WARNING - This method is deprecated and will be removed in a future release. Use synchronize/synchronize_async with sync_type=SyncType.PULL_PUSH instead.

PARAMETER DESCRIPTION
timeout

The number of seconds to wait for the job to complete or progress before raising a SynapseTimeoutError. Defaults to 120.

TYPE: int DEFAULT: 120

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
GridSession

The GridSession object with export information populated.

TYPE: Grid

RAISES DESCRIPTION
ValueError

If session_id is not provided.

Migration to synchronize_async

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid
from synapseclient.models.curation import SyncType

syn = Synapse()
syn.login()

async def main():
    grid = Grid(session_id="abc-123-def")

    # Old approach (DEPRECATED)
    # grid = await grid.export_to_record_set_async()
    # print(f"Exported to record set: {grid.record_set_id}")
    # print(f"Version number: {grid.record_set_version_number}")
    # if grid.validation_summary_statistics:
    #     print(f"Valid records: {grid.validation_summary_statistics.number_of_valid_children}")

    # New approach (RECOMMENDED)
    # Note: unlike export_to_record_set_async, synchronize_async
    # does not populate record_set_id, record_set_version_number,
    # or validation_summary_statistics on the returned Grid.
    grid = await grid.synchronize_async(sync_type=SyncType.PULL_PUSH)

asyncio.run(main())
Source code in synapseclient/models/curation.py
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
4732
4733
4734
4735
4736
4737
4738
4739
4740
4741
4742
4743
4744
4745
4746
4747
4748
4749
4750
4751
4752
4753
4754
4755
4756
4757
4758
4759
4760
4761
4762
4763
4764
4765
4766
4767
4768
4769
4770
@deprecated(
    version="4.14.0",
    reason="Use `synchronize_async` with `sync_type=SyncType.PULL_PUSH` instead.",
)
async def export_to_record_set_async(
    self, *, timeout: int = 120, synapse_client: Optional[Synapse] = None
) -> "Grid":
    """
    Exports the grid session data back to a record set. This will create a new version
    of the original record set with the modified data from the grid session.

    WARNING - This method is deprecated and will be removed in a future release.
    Use `synchronize`/`synchronize_async` with `sync_type=SyncType.PULL_PUSH` instead.

    Arguments:
        timeout: The number of seconds to wait for the job to complete or progress
            before raising a SynapseTimeoutError. Defaults to 120.
        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.

    Returns:
        GridSession: The GridSession object with export information populated.

    Raises:
        ValueError: If session_id is not provided.

    Example: Migration to synchronize_async
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid
        from synapseclient.models.curation import SyncType

        syn = Synapse()
        syn.login()

        async def main():
            grid = Grid(session_id="abc-123-def")

            # Old approach (DEPRECATED)
            # grid = await grid.export_to_record_set_async()
            # print(f"Exported to record set: {grid.record_set_id}")
            # print(f"Version number: {grid.record_set_version_number}")
            # if grid.validation_summary_statistics:
            #     print(f"Valid records: {grid.validation_summary_statistics.number_of_valid_children}")

            # New approach (RECOMMENDED)
            # Note: unlike export_to_record_set_async, synchronize_async
            # does not populate record_set_id, record_set_version_number,
            # or validation_summary_statistics on the returned Grid.
            grid = await grid.synchronize_async(sync_type=SyncType.PULL_PUSH)

        asyncio.run(main())
        ```
    """
    if not self.session_id:
        raise ValueError("session_id is required to export a GridSession")

    trace.get_current_span().set_attributes(
        {
            "synapse.session_id": self.session_id or "",
        }
    )

    # Create and send the export request
    export_request = GridRecordSetExportRequest(session_id=self.session_id)
    result = await export_request.send_job_and_wait_async(
        timeout=timeout, synapse_client=synapse_client
    )

    self.record_set_id = result.response_record_set_id
    self.record_set_version_number = result.record_set_version_number
    self.validation_summary_statistics = result.validation_summary_statistics

    return self

synchronize_async async

synchronize_async(*, sync_type: Optional[Union[SyncType, str]] = None, timeout: int = 120, synapse_client: Optional[Synapse] = None) -> Grid

Synchronizes the grid session's schema and row data against its source entity.

Grid sessions created from a file view via initial_query always perform a full PULL_PUSH. Grid sessions backed by a RecordSet may instead pass sync_type=SyncType.PULL to pull the latest RecordSet data/schema into the session for review, without immediately writing the merged result back as a new RecordSet version. Once satisfied with the result, call this method again (with sync_type omitted, or explicitly set to SyncType.PULL_PUSH) to push the merged data back to the RecordSet as a new version.

PARAMETER DESCRIPTION
sync_type

The type of synchronization to perform. Optional; the server defaults to SyncType.PULL_PUSH when omitted. SyncType.PULL is currently only supported for RecordSet-based grids.

TYPE: Optional[Union[SyncType, str]] DEFAULT: None

timeout

The number of seconds to wait for the job to complete or progress before raising a SynapseTimeoutError. Defaults to 120.

TYPE: int DEFAULT: 120

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
Grid

The Grid object.

TYPE: Grid

RAISES DESCRIPTION
ValueError

If session_id is not provided.

Synchronize a grid session created from a file view

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid
from synapseclient.models.table_components import Query

syn = Synapse()
syn.login()

async def main():
    # First create a grid session from a file view
    query = Query(sql="SELECT * FROM syn1234567")
    grid = Grid(initial_query=query)
    grid = await grid.create_async()

    # Synchronize the grid with the latest state of the file view
    grid = await grid.synchronize_async()

asyncio.run(main())
Preview a RecordSet-backed grid's merge before pushing it back

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid
from synapseclient.models.curation import SyncType

syn = Synapse()
syn.login()

async def main():
    grid = Grid(record_set_id="syn1234567")
    grid = await grid.create_async()

    # Pull in the latest RecordSet data/schema without pushing back yet
    grid = await grid.synchronize_async(sync_type=SyncType.PULL)

    # ... review the merged result in the grid session ...

    # Push the merged result back as a new RecordSet version
    grid = await grid.synchronize_async(sync_type=SyncType.PULL_PUSH)

asyncio.run(main())
Source code in synapseclient/models/curation.py
5174
5175
5176
5177
5178
5179
5180
5181
5182
5183
5184
5185
5186
5187
5188
5189
5190
5191
5192
5193
5194
5195
5196
5197
5198
5199
5200
5201
5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
5215
5216
5217
5218
5219
5220
5221
5222
5223
5224
5225
5226
5227
5228
5229
5230
5231
5232
5233
5234
5235
5236
5237
5238
5239
5240
5241
5242
5243
5244
5245
5246
5247
5248
5249
5250
5251
5252
5253
5254
5255
5256
5257
5258
5259
5260
5261
5262
5263
5264
5265
5266
5267
5268
5269
5270
5271
5272
5273
5274
5275
5276
5277
5278
5279
@otel_trace_method(
    method_to_trace_name=lambda self, **kwargs: f"Grid_Synchronize: ID: {self.session_id}"
)
async def synchronize_async(
    self,
    *,
    sync_type: Optional[Union[SyncType, str]] = None,
    timeout: int = 120,
    synapse_client: Optional[Synapse] = None,
) -> "Grid":
    """
    Synchronizes the grid session's schema and row data against its source entity.

    Grid sessions created from a file view via `initial_query` always perform a
    full PULL_PUSH. Grid sessions backed by a RecordSet may instead pass
    `sync_type=SyncType.PULL` to pull the latest RecordSet data/schema into the
    session for review, without immediately writing the merged result back as a
    new RecordSet version. Once satisfied with the result, call this method again
    (with `sync_type` omitted, or explicitly set to `SyncType.PULL_PUSH`) to push
    the merged data back to the RecordSet as a new version.

    Arguments:
        sync_type: The type of synchronization to perform. Optional; the server
            defaults to `SyncType.PULL_PUSH` when omitted. `SyncType.PULL` is
            currently only supported for RecordSet-based grids.
        timeout: The number of seconds to wait for the job to complete or progress
            before raising a SynapseTimeoutError. Defaults to 120.
        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.

    Returns:
        Grid: The Grid object.

    Raises:
        ValueError: If session_id is not provided.

    Example: Synchronize a grid session created from a file view
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid
        from synapseclient.models.table_components import Query

        syn = Synapse()
        syn.login()

        async def main():
            # First create a grid session from a file view
            query = Query(sql="SELECT * FROM syn1234567")
            grid = Grid(initial_query=query)
            grid = await grid.create_async()

            # Synchronize the grid with the latest state of the file view
            grid = await grid.synchronize_async()

        asyncio.run(main())
        ```

    Example: Preview a RecordSet-backed grid's merge before pushing it back
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid
        from synapseclient.models.curation import SyncType

        syn = Synapse()
        syn.login()

        async def main():
            grid = Grid(record_set_id="syn1234567")
            grid = await grid.create_async()

            # Pull in the latest RecordSet data/schema without pushing back yet
            grid = await grid.synchronize_async(sync_type=SyncType.PULL)

            # ... review the merged result in the grid session ...

            # Push the merged result back as a new RecordSet version
            grid = await grid.synchronize_async(sync_type=SyncType.PULL_PUSH)

        asyncio.run(main())
        ```
    """
    if not self.session_id:
        raise ValueError("session_id is required to synchronize a GridSession")

    request = SynchronizeGridRequest(
        grid_session_id=self.session_id, sync_type=sync_type
    )
    result = await request.send_job_and_wait_async(
        timeout=timeout, synapse_client=synapse_client
    )

    if result.error_messages:
        client = Synapse.get_client(synapse_client=synapse_client)
        client.logger.error(
            f"Grid session '{self.session_id}' synchronization completed with "
            f"error messages: {result.error_messages}"
        )

    return self

download_csv_async async

download_csv_async(*, destination: Optional[str] = None, write_header: bool = True, include_row_id_and_row_version: bool = False, include_etag: bool = False, csv_table_descriptor: Optional[CsvTableDescriptor] = None, file_name: Optional[str] = None, timeout: int = 120, synapse_client: Optional[Synapse] = None) -> str

Asynchronously download the current state of this grid session as a CSV file.

Submits a DownloadFromGridRequest async job, waits for it to complete, then downloads the resulting CSV to the local filesystem.

PARAMETER DESCRIPTION
destination

Local directory path where the CSV will be saved. The directory must already exist. If not provided, defaults to the current working directory.

TYPE: Optional[str] DEFAULT: None

write_header

Whether the first line should contain column names as a header. Defaults to True.

TYPE: bool DEFAULT: True

include_row_id_and_row_version

Whether the first two columns should contain row ID and version. Defaults to False.

TYPE: bool DEFAULT: False

include_etag

Whether a column should contain the row etag. Defaults to False.

TYPE: bool DEFAULT: False

csv_table_descriptor

The description of the CSV format (delimiter, quote character, etc.). If not provided, the default CSV format will be used.

TYPE: Optional[CsvTableDescriptor] DEFAULT: None

file_name

The optional name for the downloaded file. If not provided, defaults to grid_{session_id}-{timestamp}.csv.

TYPE: Optional[str] DEFAULT: None

timeout

The number of seconds to wait for the async job to complete or progress before raising a SynapseTimeoutError. Defaults to 120.

TYPE: int DEFAULT: 120

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
str

The local path to the downloaded CSV file.

RAISES DESCRIPTION
ValueError

If session_id is not provided.

Download a grid session as a CSV asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid

syn = Synapse()
syn.login()

async def main():
    grid = Grid(session_id="abc-123-def")
    path = await grid.download_csv_async(destination="./downloads")
    print(f"Downloaded CSV to: {path}")

asyncio.run(main())
Download a grid session as a CSV with a custom file name asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid

syn = Synapse()
syn.login()

async def main():
    grid = Grid(session_id="abc-123-def")
    path = await grid.download_csv_async(
        destination="./downloads", file_name="my_export.csv"
    )
    print(f"Downloaded CSV to: {path}")

asyncio.run(main())
Source code in synapseclient/models/curation.py
5033
5034
5035
5036
5037
5038
5039
5040
5041
5042
5043
5044
5045
5046
5047
5048
5049
5050
5051
5052
5053
5054
5055
5056
5057
5058
5059
5060
5061
5062
5063
5064
5065
5066
5067
5068
5069
5070
5071
5072
5073
5074
5075
5076
5077
5078
5079
5080
5081
5082
5083
5084
5085
5086
5087
5088
5089
5090
5091
5092
5093
5094
5095
5096
5097
5098
5099
5100
5101
5102
5103
5104
5105
5106
5107
5108
5109
5110
5111
5112
5113
5114
5115
5116
5117
5118
5119
5120
5121
5122
5123
5124
5125
5126
5127
5128
5129
5130
5131
5132
5133
5134
5135
5136
5137
5138
5139
5140
5141
5142
5143
5144
5145
5146
5147
5148
5149
5150
5151
5152
5153
5154
5155
5156
5157
5158
5159
5160
5161
5162
5163
5164
5165
5166
5167
5168
5169
5170
5171
5172
@otel_trace_method(
    method_to_trace_name=lambda self, *args, **kwargs: f"Grid_DownloadCsv: ID: {self.session_id}"
)
async def download_csv_async(
    self,
    *,
    destination: Optional[str] = None,
    write_header: bool = True,
    include_row_id_and_row_version: bool = False,
    include_etag: bool = False,
    csv_table_descriptor: Optional[CsvTableDescriptor] = None,
    file_name: Optional[str] = None,
    timeout: int = 120,
    synapse_client: Optional[Synapse] = None,
) -> str:
    """
    Asynchronously download the current state of this grid session as a CSV file.

    Submits a DownloadFromGridRequest async job, waits for it to complete,
    then downloads the resulting CSV to the local filesystem.

    Arguments:
        destination: Local directory path where the CSV will be saved. The directory must already exist.
            If not provided, defaults to the current working directory.
        write_header: Whether the first line should contain column names
            as a header. Defaults to True.
        include_row_id_and_row_version: Whether the first two columns
            should contain row ID and version. Defaults to False.
        include_etag: Whether a column should contain the row etag.
            Defaults to False.
        csv_table_descriptor: The description of the CSV format (delimiter,
            quote character, etc.). If not provided, the default CSV format
            will be used.
        file_name: The optional name for the downloaded file. If not
            provided, defaults to `grid_{session_id}-{timestamp}.csv`.
        timeout: The number of seconds to wait for the async job to
            complete or progress before raising a SynapseTimeoutError.
            Defaults to 120.
        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.

    Returns:
        The local path to the downloaded CSV file.

    Raises:
        ValueError: If session_id is not provided.

    Example: Download a grid session as a CSV asynchronously
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid

        syn = Synapse()
        syn.login()

        async def main():
            grid = Grid(session_id="abc-123-def")
            path = await grid.download_csv_async(destination="./downloads")
            print(f"Downloaded CSV to: {path}")

        asyncio.run(main())
        ```

    Example: Download a grid session as a CSV with a custom file name asynchronously
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid

        syn = Synapse()
        syn.login()

        async def main():
            grid = Grid(session_id="abc-123-def")
            path = await grid.download_csv_async(
                destination="./downloads", file_name="my_export.csv"
            )
            print(f"Downloaded CSV to: {path}")

        asyncio.run(main())
        ```
    """
    if not self.session_id:
        raise ValueError("session_id is required to download a GridSession as CSV")

    if not destination:
        destination = os.getcwd()

    if not os.path.isdir(destination):
        raise ValueError(f"Destination {destination} is not a valid directory.")

    trace.get_current_span().set_attributes({"synapse.session_id": self.session_id})

    effective_descriptor = csv_table_descriptor or CsvTableDescriptor()
    request = DownloadFromGridRequest(
        session_id=self.session_id,
        write_header=write_header,
        include_row_id_and_row_version=include_row_id_and_row_version,
        include_etag=include_etag,
        csv_table_descriptor=effective_descriptor,
        file_name=file_name,
    )
    download_response = await request.send_job_and_wait_async(
        timeout=timeout, synapse_client=synapse_client
    )
    if not download_response.results_file_handle_id:
        raise ValueError(
            f"Download job for grid session '{self.session_id}' completed but "
            "did not return a file handle ID. The CSV result may be empty or "
            "the job may have failed silently."
        )
    file_handle, presigned_url = await asyncio.gather(
        get_file_handle(
            file_handle_id=download_response.results_file_handle_id,
            synapse_client=synapse_client,
        ),
        get_file_handle_presigned_url(
            file_handle_id=download_response.results_file_handle_id,
            synapse_client=synapse_client,
        ),
    )
    if not file_name:
        timestamp = datetime.now(tz=timezone.utc).strftime("%Y%m%d%H%M%S")
        file_name = f"grid_{self.session_id}-{timestamp}.csv"
    file_path = os.path.join(destination, file_name)
    return await asyncio.to_thread(
        download_from_url,
        url=presigned_url,
        destination=file_path,
        file_handle_id=file_handle["id"],
        expected_md5=file_handle.get("contentMd5"),
        url_is_presigned=True,
        synapse_client=synapse_client,
    )

import_csv_async async

import_csv_async(path: str, *, timeout: int = 120, csv_table_descriptor: Optional[CsvTableDescriptor] = None, synapse_client: Optional[Synapse] = None) -> Grid

Import a CSV file into this grid session. Previews the file to determine the column schema, then imports the data. Currently supports only grids created from a record set.

PARAMETER DESCRIPTION
path

Local path to the CSV file to import.

TYPE: str

csv_table_descriptor

The description of the CSV format (delimiter, quote character, etc.). If not provided, the default CSV format will be used.

TYPE: Optional[CsvTableDescriptor] DEFAULT: None

timeout

The number of seconds to wait for each async job to complete or progress before raising a SynapseTimeoutError. Defaults to 120.

TYPE: int DEFAULT: 120

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
Grid

The Grid object.

RAISES DESCRIPTION
ValueError

If session_id is not provided.

Import a CSV file into a grid session asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid

syn = Synapse()
syn.login()

async def main():
    grid = Grid(session_id="abc-123-def")
    grid = await grid.import_csv_async(path="/local/path/to/data.csv")
    print(f"Import complete for session: {grid.session_id}")

asyncio.run(main())
Source code in synapseclient/models/curation.py
4925
4926
4927
4928
4929
4930
4931
4932
4933
4934
4935
4936
4937
4938
4939
4940
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
4966
4967
4968
4969
4970
4971
4972
4973
4974
4975
4976
4977
4978
4979
4980
4981
4982
4983
4984
4985
4986
4987
4988
4989
4990
4991
4992
4993
4994
4995
4996
4997
4998
4999
5000
5001
5002
5003
5004
5005
5006
5007
5008
5009
5010
5011
5012
5013
5014
5015
5016
5017
5018
5019
5020
5021
5022
5023
5024
5025
5026
5027
5028
5029
5030
5031
@otel_trace_method(
    method_to_trace_name=lambda self, **kwargs: f"Grid_ImportCsv: ID: {self.session_id}"
)
async def import_csv_async(
    self,
    path: str,
    *,
    timeout: int = 120,
    csv_table_descriptor: Optional[CsvTableDescriptor] = None,
    synapse_client: Optional[Synapse] = None,
) -> "Grid":
    """
    Import a CSV file into this grid session. Previews the file to determine
    the column schema, then imports the data. Currently supports only grids
    created from a record set.

    Arguments:
        path: Local path to the CSV file to import.
        csv_table_descriptor: The description of the CSV format (delimiter,
            quote character, etc.). If not provided, the default CSV format
            will be used.
        timeout: The number of seconds to wait for each async job to complete
            or progress before raising a SynapseTimeoutError. Defaults to 120.
        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.

    Returns:
        The Grid object.

    Raises:
        ValueError: If session_id is not provided.

    Example: Import a CSV file into a grid session asynchronously
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid

        syn = Synapse()
        syn.login()

        async def main():
            grid = Grid(session_id="abc-123-def")
            grid = await grid.import_csv_async(path="/local/path/to/data.csv")
            print(f"Import complete for session: {grid.session_id}")

        asyncio.run(main())
        ```
    """

    if not self.session_id:
        raise ValueError(
            "session_id is required to import a CSV into a GridSession"
        )

    if not os.path.isfile(path):
        raise ValueError(f"Path '{path}' is not a valid file.")

    trace.get_current_span().set_attributes(
        {
            "synapse.session_id": self.session_id,
        }
    )

    client = Synapse.get_client(synapse_client=synapse_client)
    file_handle = await upload_synapse_s3(syn=client, file_path=path)
    file_handle_id = file_handle["id"]

    effective_descriptor = csv_table_descriptor or CsvTableDescriptor()

    upload_to_table_preview = UploadToTablePreviewRequest(
        csv_table_descriptor=effective_descriptor,
        upload_file_handle_id=file_handle_id,
    )

    preview_response = await upload_to_table_preview.send_job_and_wait_async(
        timeout=timeout, synapse_client=synapse_client
    )
    if not preview_response.suggested_columns:
        raise ValueError(
            f"CSV preview for file handle {file_handle_id} returned no suggested "
            f"columns (rows scanned: {preview_response.rows_scanned}). The file may "
            f"be empty, contain only a header row, or use a separator different "
            f"from the configured csv_table_descriptor "
            f"(separator={repr(effective_descriptor.separator)})."
        )

    import_request = GridCsvImportRequest(
        session_id=self.session_id,
        file_handle_id=file_handle_id,
        schema=preview_response.suggested_columns,
        csv_descriptor=effective_descriptor,
    )
    import_response = await import_request.send_job_and_wait_async(
        timeout=timeout, synapse_client=synapse_client
    )
    client.logger.info(
        f"CSV import to grid session {self.session_id} completed successfully, "
        f"total count: {import_response.total_count}, "
        f"total created: {import_response.created_count}, "
        f"total updated: {import_response.updated_count}"
    )

    return self

delete_async async

delete_async(*, synapse_client: Optional[Synapse] = None) -> None

Delete the grid session.

Note: Only the user that created a grid session may delete it.

PARAMETER DESCRIPTION
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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
None

None

RAISES DESCRIPTION
ValueError

If session_id is not provided.

Delete a grid session asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid

syn = Synapse()
syn.login()

async def main():
    # Delete the grid session
    grid = Grid(session_id="abc-123-def")
    await grid.delete_async()
    print("Grid session deleted successfully")

asyncio.run(main())
Source code in synapseclient/models/curation.py
4872
4873
4874
4875
4876
4877
4878
4879
4880
4881
4882
4883
4884
4885
4886
4887
4888
4889
4890
4891
4892
4893
4894
4895
4896
4897
4898
4899
4900
4901
4902
4903
4904
4905
4906
4907
4908
4909
4910
4911
4912
4913
4914
4915
4916
4917
4918
4919
4920
4921
4922
4923
@otel_trace_method(
    method_to_trace_name=lambda self, **kwargs: f"Grid_Delete: ID: {self.session_id}"
)
async def delete_async(self, *, synapse_client: Optional[Synapse] = None) -> None:
    """
    Delete the grid session.

    Note: Only the user that created a grid session may delete it.

    Arguments:
        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.

    Returns:
        None

    Raises:
        ValueError: If session_id is not provided.

    Example: Delete a grid session asynchronously
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid

        syn = Synapse()
        syn.login()

        async def main():
            # Delete the grid session
            grid = Grid(session_id="abc-123-def")
            await grid.delete_async()
            print("Grid session deleted successfully")

        asyncio.run(main())
        ```
    """
    if not self.session_id:
        raise ValueError("session_id is required to delete a GridSession")

    trace.get_current_span().set_attributes(
        {
            "synapse.session_id": self.session_id or "",
        }
    )

    await delete_grid_session(
        session_id=self.session_id, synapse_client=synapse_client
    )

list_async async classmethod

list_async(source_id: Optional[str] = None, *, synapse_client: Optional[Synapse] = None) -> AsyncGenerator[Grid, None]

Generator to get a list of active grid sessions for the user.

PARAMETER DESCRIPTION
source_id

Optional. When provided, only sessions with this synId will be returned.

TYPE: Optional[str] DEFAULT: None

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.

TYPE: Optional[Synapse] DEFAULT: None

YIELDS DESCRIPTION
AsyncGenerator[Grid, None]

Grid objects representing active grid sessions.

List all active grid sessions asynchronously

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid

syn = Synapse()
syn.login()

async def main():
    # List all active grid sessions for the user
    async for grid in Grid.list_async():
        print(f"Session ID: {grid.session_id}")
        print(f"Source Entity: {grid.source_entity_id}")
        print(f"Started: {grid.started_on}")
        print("---")

    # List grid sessions for a specific source
    async for grid in Grid.list_async(source_id="syn1234567"):
        print(f"Session ID: {grid.session_id}")
        print(f"Modified: {grid.modified_on}")

asyncio.run(main())
Source code in synapseclient/models/curation.py
4792
4793
4794
4795
4796
4797
4798
4799
4800
4801
4802
4803
4804
4805
4806
4807
4808
4809
4810
4811
4812
4813
4814
4815
4816
4817
4818
4819
4820
4821
4822
4823
4824
4825
4826
4827
4828
4829
4830
4831
4832
4833
4834
4835
4836
4837
4838
4839
4840
4841
4842
4843
4844
4845
@skip_async_to_sync
@classmethod
async def list_async(
    cls,
    source_id: Optional[str] = None,
    *,
    synapse_client: Optional[Synapse] = None,
) -> AsyncGenerator["Grid", None]:
    """
    Generator to get a list of active grid sessions for the user.

    Arguments:
        source_id: Optional. When provided, only sessions with this synId will be returned.
        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.

    Yields:
        Grid objects representing active grid sessions.

    Example: List all active grid sessions asynchronously
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid

        syn = Synapse()
        syn.login()

        async def main():
            # List all active grid sessions for the user
            async for grid in Grid.list_async():
                print(f"Session ID: {grid.session_id}")
                print(f"Source Entity: {grid.source_entity_id}")
                print(f"Started: {grid.started_on}")
                print("---")

            # List grid sessions for a specific source
            async for grid in Grid.list_async(source_id="syn1234567"):
                print(f"Session ID: {grid.session_id}")
                print(f"Modified: {grid.modified_on}")

        asyncio.run(main())
        ```
    """
    async for session_dict in list_grid_sessions(
        source_id=source_id, synapse_client=synapse_client
    ):
        # Convert the dictionary to a Grid object
        grid = cls()
        grid.fill_from_dict(session_dict)
        yield grid

connect_async async

connect_async(*, attach_to_previous_session: bool = False, timeout: int = 120, synapse_client: Optional[Synapse] = None) -> AsyncGenerator[Grid, None]

Connects to a grid session and binds a single replica to it for the duration of the async with block.

If session_id is not already set (e.g. from create_grid_session), creates a new grid session first via record_set_id or initial_query, same as create_async. Either way, one replica is then created (see _create_replica_async) that is reused by every validate_rows_async call made within the block.

PARAMETER DESCRIPTION
attach_to_previous_session

Only applies when creating a new session from record_set_id. If True, attaches to an existing active session instead of creating a new one. Defaults to False.

TYPE: bool DEFAULT: False

timeout

The number of seconds to wait for the job to complete or progress before raising a SynapseTimeoutError. Defaults to 120.

TYPE: int DEFAULT: 120

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.

TYPE: Optional[Synapse] DEFAULT: None

YIELDS DESCRIPTION
AsyncGenerator[Grid, None]

The connected Grid, with a replica bound to it.

Validate rows using a newly created grid session

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid
from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll

syn = Synapse()
syn.login()

async def main():
    async with Grid(record_set_id="syn1234567").connect_async() as session:
        query_request = QueryRequest(
            query=GridQuery(column_selection=[SelectAll()])
        )
        result = await session.validate_rows_async(query_request=query_request)
        for row in result.rows:
            print(f"Row ID: {row.row_id}, Validation Result: {row.validation_results}")

asyncio.run(main())
Validate rows using an existing grid session

 

If a session_id is already set, connect_async will not create a new grid session

import asyncio
from synapseclient import Synapse
from synapseclient.models import CurationTask, Grid
from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll

syn = Synapse()
syn.login()

task = CurationTask(task_id="1234")
grid = task.create_grid_session()

async def main():
    async with grid.connect_async() as session:
        query_request = QueryRequest(
            query=GridQuery(column_selection=[SelectAll()])
        )
        result = await session.validate_rows_async(query_request=query_request)
        for row in result.rows:
            print(f"Row ID: {row.row_id}, Validation Result: {row.validation_results}")

asyncio.run(main())
Source code in synapseclient/models/curation.py
5349
5350
5351
5352
5353
5354
5355
5356
5357
5358
5359
5360
5361
5362
5363
5364
5365
5366
5367
5368
5369
5370
5371
5372
5373
5374
5375
5376
5377
5378
5379
5380
5381
5382
5383
5384
5385
5386
5387
5388
5389
5390
5391
5392
5393
5394
5395
5396
5397
5398
5399
5400
5401
5402
5403
5404
5405
5406
5407
5408
5409
5410
5411
5412
5413
5414
5415
5416
5417
5418
5419
5420
5421
5422
5423
5424
5425
5426
5427
5428
5429
5430
5431
5432
5433
5434
5435
5436
5437
5438
5439
5440
5441
5442
5443
5444
5445
5446
5447
5448
5449
5450
5451
5452
5453
5454
@skip_async_to_sync
@asynccontextmanager
async def connect_async(
    self,
    *,
    attach_to_previous_session: bool = False,
    timeout: int = 120,
    synapse_client: Optional[Synapse] = None,
) -> AsyncGenerator["Grid", None]:
    """
    Connects to a grid session and binds a single replica to it for the
    duration of the `async with` block.

    If `session_id` is not already set (e.g. from `create_grid_session`),
    creates a new grid session first via `record_set_id` or
    `initial_query`, same as `create_async`. Either way, one replica is
    then created (see `_create_replica_async`) that is reused by every
    `validate_rows_async` call made within the block.

    Arguments:
        attach_to_previous_session: Only applies when creating a new
            session from `record_set_id`. If True, attaches to an existing
            active session instead of creating a new one. Defaults to False.
        timeout: The number of seconds to wait for the job to complete or progress
            before raising a SynapseTimeoutError. Defaults to 120.
        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.

    Yields:
        The connected Grid, with a replica bound to it.

    Example: Validate rows using a newly created grid session
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid
        from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll

        syn = Synapse()
        syn.login()

        async def main():
            async with Grid(record_set_id="syn1234567").connect_async() as session:
                query_request = QueryRequest(
                    query=GridQuery(column_selection=[SelectAll()])
                )
                result = await session.validate_rows_async(query_request=query_request)
                for row in result.rows:
                    print(f"Row ID: {row.row_id}, Validation Result: {row.validation_results}")

        asyncio.run(main())
        ```

    Example: Validate rows using an existing grid session
        &nbsp;

        If a session_id is already set, connect_async will not create a new
        grid session

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import CurationTask, Grid
        from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll

        syn = Synapse()
        syn.login()

        task = CurationTask(task_id="1234")
        grid = task.create_grid_session()

        async def main():
            async with grid.connect_async() as session:
                query_request = QueryRequest(
                    query=GridQuery(column_selection=[SelectAll()])
                )
                result = await session.validate_rows_async(query_request=query_request)
                for row in result.rows:
                    print(f"Row ID: {row.row_id}, Validation Result: {row.validation_results}")

        asyncio.run(main())
        ```
    """
    trace.get_current_span().set_attributes(
        {
            "synapse.record_set_id": self.record_set_id or "",
            "synapse.session_id": self.session_id or "",
        }
    )

    if not self.session_id:
        await self.create_async(
            attach_to_previous_session=attach_to_previous_session,
            timeout=timeout,
            synapse_client=synapse_client,
        )

    replica = await self._create_replica_async(synapse_client=synapse_client)
    self._replica_id = replica.replica_id
    try:
        yield self
    finally:
        self._replica_id = None

validate_rows_async async

validate_rows_async(*, timeout: int = 120, query_request: QueryRequest, synapse_client: Optional[Synapse] = None) -> Optional[GridQueryResult]

Queries this grid session's rows and returns their per-row validation results against the grid's bound JSON schema.

This Grid must have been obtained from connect_async (or connect), which binds a replica to it. The given query_request is then submitted to the grid session using that replica, and this waits for the job to complete.

PARAMETER DESCRIPTION
timeout

The number of seconds to wait for the job to complete or progress before raising a SynapseTimeoutError. Defaults to 120.

TYPE: int DEFAULT: 120

query_request

The structured query to run against the grid, wrapping a GridQuery that defines the column selection, filters, and whether to include the detailed all_validation_messages list on each row.

TYPE: QueryRequest

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.

TYPE: Optional[Synapse] DEFAULT: None

RETURNS DESCRIPTION
Optional[GridQueryResult]

The GridQueryResult containing the selected columns and rows, each with its own validation_results, or None if the completed job did not return a query_result. Logs a warning if the job completed but no rows matched the query.

RAISES DESCRIPTION
ValueError

If session_id is not provided, or if no replica is bound to this Grid (see connect_async/connect).

Validate every row of a grid session

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid
from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll

syn = Synapse()
syn.login()

async def main():
    async with Grid(record_set_id="syn1234567").connect_async() as grid:
        # SelectAll() selects every column in the grid, so each row's
        # full data is returned alongside its validation results.
        query_request = QueryRequest(
            query=GridQuery(column_selection=[SelectAll()])
        )
        query_result = await grid.validate_rows_async(query_request=query_request)

        for row in query_result.rows:
            validation = row.validation_results
            print(f"Row ID: {row.row_id}, Validation Result: {validation}")

asyncio.run(main())
Validate only the currently invalid rows of a grid session

 

import asyncio
from synapseclient import Synapse
from synapseclient.models import Grid
from synapseclient.models.curation import (
    GridQuery,
    QueryRequest,
    RowIsValidFilter,
    SelectAll,
)

syn = Synapse()
syn.login()

async def main():
    async with Grid(record_set_id="syn1234567").connect_async() as grid:
        # Filter to only the rows that are currently invalid, and request
        # the detailed allValidationMessages list on each one.
        query_request = QueryRequest(
            query=GridQuery(
                column_selection=[SelectAll()],
                filters=[RowIsValidFilter(value=False)],
                include_validation_messages=True,
            )
        )
        query_result = await grid.validate_rows_async(query_request=query_request)

        for row in query_result.rows:
            print(f"Invalid row {row.row_id}: {row.validation_results}")

asyncio.run(main())
Source code in synapseclient/models/curation.py
5556
5557
5558
5559
5560
5561
5562
5563
5564
5565
5566
5567
5568
5569
5570
5571
5572
5573
5574
5575
5576
5577
5578
5579
5580
5581
5582
5583
5584
5585
5586
5587
5588
5589
5590
5591
5592
5593
5594
5595
5596
5597
5598
5599
5600
5601
5602
5603
5604
5605
5606
5607
5608
5609
5610
5611
5612
5613
5614
5615
5616
5617
5618
5619
5620
5621
5622
5623
5624
5625
5626
5627
5628
5629
5630
5631
5632
5633
5634
5635
5636
5637
5638
5639
5640
5641
5642
5643
5644
5645
5646
5647
5648
5649
5650
5651
5652
5653
5654
5655
5656
5657
5658
5659
5660
5661
5662
5663
5664
5665
5666
5667
5668
5669
5670
5671
5672
5673
5674
5675
5676
5677
5678
5679
5680
5681
5682
@otel_trace_method(
    method_to_trace_name=lambda self, **kwargs: f"Grid_Validate_Rows_Session_ID: {self.session_id}"
)
async def validate_rows_async(
    self,
    *,
    timeout: int = 120,
    query_request: QueryRequest,
    synapse_client: Optional[Synapse] = None,
) -> Optional["GridQueryResult"]:
    """
    Queries this grid session's rows and returns their per-row validation
    results against the grid's bound JSON schema.

    This Grid must have been obtained from `connect_async` (or `connect`),
    which binds a replica to it. The given query_request is then submitted
    to the grid session using that replica, and this waits for the job to
    complete.

    Arguments:
        timeout: The number of seconds to wait for the job to complete or progress
            before raising a SynapseTimeoutError. Defaults to 120.
        query_request: The structured query to run against the grid, wrapping a
            GridQuery that defines the column selection, filters, and whether to
            include the detailed `all_validation_messages` list on each row.
        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.

    Returns:
        The GridQueryResult containing the selected columns and rows, each with its own validation_results, or None if the completed job did not return a query_result. Logs a warning if the job completed but no rows matched the query.

    Raises:
        ValueError: If session_id is not provided, or if no replica is bound
            to this Grid (see `connect_async`/`connect`).

    Example: Validate every row of a grid session
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid
        from synapseclient.models.curation import GridQuery, QueryRequest, SelectAll

        syn = Synapse()
        syn.login()

        async def main():
            async with Grid(record_set_id="syn1234567").connect_async() as grid:
                # SelectAll() selects every column in the grid, so each row's
                # full data is returned alongside its validation results.
                query_request = QueryRequest(
                    query=GridQuery(column_selection=[SelectAll()])
                )
                query_result = await grid.validate_rows_async(query_request=query_request)

                for row in query_result.rows:
                    validation = row.validation_results
                    print(f"Row ID: {row.row_id}, Validation Result: {validation}")

        asyncio.run(main())
        ```

    Example: Validate only the currently invalid rows of a grid session
        &nbsp;

        ```python
        import asyncio
        from synapseclient import Synapse
        from synapseclient.models import Grid
        from synapseclient.models.curation import (
            GridQuery,
            QueryRequest,
            RowIsValidFilter,
            SelectAll,
        )

        syn = Synapse()
        syn.login()

        async def main():
            async with Grid(record_set_id="syn1234567").connect_async() as grid:
                # Filter to only the rows that are currently invalid, and request
                # the detailed allValidationMessages list on each one.
                query_request = QueryRequest(
                    query=GridQuery(
                        column_selection=[SelectAll()],
                        filters=[RowIsValidFilter(value=False)],
                        include_validation_messages=True,
                    )
                )
                query_result = await grid.validate_rows_async(query_request=query_request)

                for row in query_result.rows:
                    print(f"Invalid row {row.row_id}: {row.validation_results}")

        asyncio.run(main())
        ```
    """
    if not self.session_id:
        raise ValueError("session_id is required to validate rows")

    if self._replica_id is None:
        raise ValueError(
            "No replica is bound to this Grid. Use `connect_async` (or "
            "`connect`) to connect to a grid session before calling "
            "validate_rows_async."
        )

    request = GridQueryJobRequest(
        session_id=self.session_id,
        replica_id=self._replica_id,
        query_request=query_request,
    )
    request = await request.send_job_and_wait_async(
        timeout=timeout, synapse_client=synapse_client
    )

    if not request.query_result or not request.query_result.rows:
        client = Synapse.get_client(synapse_client=synapse_client)
        client.logger.warning(
            f"Validation job for grid session '{self.session_id}' completed but "
            "did not return any row validation results. This grid may not have "
            "any rows matching the query."
        )
    return request.query_result

synapseclient.models.Query dataclass

Represents a SQL query with optional parameters.

This result is modeled from: https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/table/Query.html

Source code in synapseclient/models/table_components.py
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
@dataclass
class Query:
    """
    Represents a SQL query with optional parameters.

    This result is modeled from: <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/table/Query.html>
    """

    sql: str
    """The SQL query string"""

    additional_filters: Optional[List[Dict[str, Any]]] = None
    """Appends additional filters to the SQL query. These are applied before facets.
    Filters within the list have an AND relationship. If a WHERE clause already exists
    on the SQL query or facets are selected, it will also be ANDed with the query
    generated by these additional filters."""
    """TODO: create QueryFilter dataclass: https://sagebionetworks.jira.com/browse/SYNPY-1651"""

    selected_facets: Optional[List[Dict[str, Any]]] = None
    """The selected facet filters"""
    """TODO: create FacetColumnRequest dataclass: https://sagebionetworks.jira.com/browse/SYNPY-1651"""

    include_entity_etag: Optional[bool] = False
    """Optional, default false. When true, a query results against views will include
    the Etag of each entity in the results. Note: The etag is necessary to update
    Entities in the view."""

    select_file_column: Optional[int] = None
    """The id of the column used to select file entities (e.g. to fetch the action
    required for download). The column needs to be an ENTITYID type column and be
    part of the schema of the underlying table/view."""

    select_file_version_column: Optional[int] = None
    """The id of the column used as the version for selecting file entities when required
    (e.g. to add a materialized view query to the download cart with version enabled).
    The column needs to be an INTEGER type column and be part of the schema of the
    underlying table/view."""

    offset: Optional[int] = None
    """The optional offset into the results"""

    limit: Optional[int] = None
    """The optional limit to the results"""

    sort: Optional[List[Dict[str, Any]]] = None
    """The sort order for the query results (ARRAY<SortItem>)"""
    """TODO: Add SortItem dataclass: https://sagebionetworks.jira.com/browse/SYNPY-1651 """

    def to_synapse_request(self) -> Dict[str, Any]:
        """Converts the Query object into a dictionary that can be passed into the REST API."""
        result = {
            "sql": self.sql,
            "additionalFilters": self.additional_filters,
            "selectedFacets": self.selected_facets,
            "includeEntityEtag": self.include_entity_etag,
            "selectFileColumn": self.select_file_column,
            "selectFileVersionColumn": self.select_file_version_column,
            "offset": self.offset,
            "limit": self.limit,
            "sort": self.sort,
        }
        delete_none_keys(result)
        return result

Attributes

sql instance-attribute

sql: str

The SQL query string

additional_filters class-attribute instance-attribute

additional_filters: Optional[List[Dict[str, Any]]] = None

Appends additional filters to the SQL query. These are applied before facets. Filters within the list have an AND relationship. If a WHERE clause already exists on the SQL query or facets are selected, it will also be ANDed with the query generated by these additional filters.

selected_facets class-attribute instance-attribute

selected_facets: Optional[List[Dict[str, Any]]] = None

The selected facet filters

include_entity_etag class-attribute instance-attribute

include_entity_etag: Optional[bool] = False

Optional, default false. When true, a query results against views will include the Etag of each entity in the results. Note: The etag is necessary to update Entities in the view.

select_file_column class-attribute instance-attribute

select_file_column: Optional[int] = None

The id of the column used to select file entities (e.g. to fetch the action required for download). The column needs to be an ENTITYID type column and be part of the schema of the underlying table/view.

select_file_version_column class-attribute instance-attribute

select_file_version_column: Optional[int] = None

The id of the column used as the version for selecting file entities when required (e.g. to add a materialized view query to the download cart with version enabled). The column needs to be an INTEGER type column and be part of the schema of the underlying table/view.

offset class-attribute instance-attribute

offset: Optional[int] = None

The optional offset into the results

limit class-attribute instance-attribute

limit: Optional[int] = None

The optional limit to the results

sort class-attribute instance-attribute

sort: Optional[List[Dict[str, Any]]] = None

The sort order for the query results (ARRAY)


synapseclient.models.curation.GridQuery dataclass

A structured grid query, expressed with JSON SelectItem and Filter objects rather than SQL syntax.

Represents a Synapse Query.

Note: This is distinct from the SQL-based table Query (org.sagebionetworks.repo.model.table.Query, imported here as Query), which takes a SQL string rather than structured select items and filters.

ATTRIBUTE DESCRIPTION
column_selection

One or more SelectItem is required to define the columns that will be returned by this query (e.g. SelectAll, CountStar).

TYPE: list[SelectItem]

filters

Each filter must be a complete JSON object with the required 'concreteType' property. Multiple filters are combined with AND logic.

TYPE: Optional[list[Filter]]

limit

Limit of the number of rows returned to avoid loading more data than needed into your context window.

TYPE: Optional[int]

offset

Specifies where the first returned row begins in the result set.

TYPE: Optional[int]

include_validation_messages

Controls whether the 'allValidationMessages' array appears in the response. Defaults to false to conserve token usage.

TYPE: Optional[bool]

Source code in synapseclient/models/curation.py
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
@dataclass
class GridQuery:
    """
    A structured grid query, expressed with JSON SelectItem and Filter objects
    rather than SQL syntax.

    Represents a [Synapse Query](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/Query.html).

    Note: This is distinct from the SQL-based table
    [Query](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/table/Query.html)
    (`org.sagebionetworks.repo.model.table.Query`, imported here as `Query`), which
    takes a SQL string rather than structured select items and filters.

    Attributes:
        column_selection: One or more SelectItem is required to define the
            columns that will be returned by this query (e.g. SelectAll,
            CountStar).
        filters: Each filter must be a complete JSON object with the required
            'concreteType' property. Multiple filters are combined with AND logic.
        limit: Limit of the number of rows returned to avoid loading more data
            than needed into your context window.
        offset: Specifies where the first returned row begins in the result set.
        include_validation_messages: Controls whether the
            'allValidationMessages' array appears in the response. Defaults to
            false to conserve token usage.
    """

    column_selection: list[SelectItem] = field(default_factory=list)
    """One or more SelectItem is required to define the columns that will be
    returned by this query."""

    limit: Optional[int] = None
    """Limit of the number of rows returned to avoid loading more data than
    needed into your context window."""

    filters: Optional[list[Filter]] = None
    """Each filter must be a complete JSON object with the required
    'concreteType' property. Multiple filters are combined with AND logic."""

    offset: Optional[int] = None
    """Specifies where the first returned row begins in the result set."""

    include_validation_messages: Optional[bool] = None
    """Controls whether the 'allValidationMessages' array appears in the response."""

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.

        Raises:
            ValueError: If column_selection is empty.
        """
        if not self.column_selection:
            raise ValueError(
                "column_selection is required and must contain at least one "
                "SelectItem."
            )

        request_dict = {
            "columnSelection": [
                item.to_synapse_request() for item in self.column_selection
            ],
            "filters": (
                [item.to_synapse_request() for item in self.filters]
                if self.filters is not None
                else None
            ),
            "limit": self.limit,
            "offset": self.offset,
            "includeValidationMessages": self.include_validation_messages,
        }
        delete_none_keys(request_dict)
        return request_dict

Attributes

column_selection class-attribute instance-attribute

column_selection: list[SelectItem] = field(default_factory=list)

One or more SelectItem is required to define the columns that will be returned by this query.

limit class-attribute instance-attribute

limit: Optional[int] = None

Limit of the number of rows returned to avoid loading more data than needed into your context window.

filters class-attribute instance-attribute

filters: Optional[list[Filter]] = None

Each filter must be a complete JSON object with the required 'concreteType' property. Multiple filters are combined with AND logic.

offset class-attribute instance-attribute

offset: Optional[int] = None

Specifies where the first returned row begins in the result set.

include_validation_messages class-attribute instance-attribute

include_validation_messages: Optional[bool] = None

Controls whether the 'allValidationMessages' array appears in the response.


synapseclient.models.curation.QueryRequest dataclass

Request to run a query.

Represents a Synapse QueryRequest.

ATTRIBUTE DESCRIPTION
query

Defines a structured query using JSON SelectItems and Filters objects - NOT SQL syntax.

TYPE: Optional[GridQuery]

Source code in synapseclient/models/curation.py
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
@dataclass
class QueryRequest:
    """
    Request to run a query.

    Represents a [Synapse QueryRequest](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/QueryRequest.html).

    Attributes:
        query: Defines a structured query using JSON SelectItems and Filters objects - NOT SQL syntax.
    """

    query: Optional[GridQuery] = None
    """Defines a structured query using JSON SelectItems and Filters objects (not SQL syntax)."""

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        request_dict = {
            "query": self.query.to_synapse_request() if self.query else None,
        }
        delete_none_keys(request_dict)
        return request_dict

Attributes

query class-attribute instance-attribute

query: Optional[GridQuery] = None

Defines a structured query using JSON SelectItems and Filters objects (not SQL syntax).


synapseclient.models.curation.SelectItem dataclass

Bases: ABC

A generic select item.

https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/SelectItem.html

Known implementations: SelectByName, SelectAll, CountStar, SelectSelection.

The concrete subclass is determined by the concreteType field in the REST response.

Source code in synapseclient/models/curation.py
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
@dataclass
class SelectItem(ABC):
    """
    A generic select item.

    <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/SelectItem.html>

    Known implementations: SelectByName, SelectAll, CountStar, SelectSelection.

    The concrete subclass is determined by the concreteType field in the REST response.
    """

    @abstractmethod
    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        ...

synapseclient.models.curation.SelectByName dataclass

Bases: SelectItem

A SelectItem that will result in the selection of a single column by its name.

https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/SelectByName.html

ATTRIBUTE DESCRIPTION
column_name

The name of the column to include in the select.

TYPE: Optional[str]

Source code in synapseclient/models/curation.py
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
@dataclass
class SelectByName(SelectItem):
    """
    A SelectItem that will result in the selection of a single column by its name.

    <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/SelectByName.html>

    Attributes:
        column_name: The name of the column to include in the select.
    """

    column_name: Optional[str] = None
    """The name of the column to include in the select."""

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        request_dict = {"concreteType": SELECT_BY_NAME, "columnName": self.column_name}
        delete_none_keys(request_dict)
        return request_dict

Attributes

column_name class-attribute instance-attribute

column_name: Optional[str] = None

The name of the column to include in the select.


synapseclient.models.curation.SelectAll dataclass

Bases: SelectItem

A SelectItem that will result in the selection of all columns.

https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/SelectAll.html

Source code in synapseclient/models/curation.py
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
@dataclass
class SelectAll(SelectItem):
    """
    A SelectItem that will result in the selection of all columns.

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

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        return {"concreteType": SELECT_ALL}

synapseclient.models.curation.CountStar dataclass

Bases: SelectItem

Use this to count the total number of rows that match the query. For example, for a user request like 'how many rows are there in total?', select this item. The alias property can be used to name the resulting count column.

https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/function/CountStar.html

ATTRIBUTE DESCRIPTION
alias

Used to name the resulting count column.

TYPE: Optional[str]

Source code in synapseclient/models/curation.py
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
@dataclass
class CountStar(SelectItem):
    """
    Use this to count the total number of rows that match the query. For example,
    for a user request like 'how many rows are there in total?', select this item.
    The alias property can be used to name the resulting count column.

    <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/function/CountStar.html>

    Attributes:
        alias: Used to name the resulting count column.
    """

    alias: Optional[str] = None
    """Used to name the resulting count column."""

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        request_dict = {"concreteType": COUNT_STAR, "alias": self.alias}
        delete_none_keys(request_dict)
        return request_dict

Attributes

alias class-attribute instance-attribute

alias: Optional[str] = None

Used to name the resulting count column.


synapseclient.models.curation.SelectSelection dataclass

Bases: SelectItem

A SelectItem that will result in the selection of the columns the user has actively selected in the interface.

https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/SelectSelection.html

Source code in synapseclient/models/curation.py
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
@dataclass
class SelectSelection(SelectItem):
    """
    A SelectItem that will result in the selection of the columns the user has
    actively selected in the interface.

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

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        return {"concreteType": SELECT_SELECTION}

synapseclient.models.curation.Filter dataclass

Bases: ABC

There are five different types of filters that can be applied to a grid query.

https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/Filter.html

Known implementations: RowValidationResultFilter, CellValueFilter, RowSelectionFilter, RowIsValidFilter, RowIdFilter.

The concrete subclass is determined by the concreteType field in the REST response.

Source code in synapseclient/models/curation.py
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
@dataclass
class Filter(ABC):
    """
    There are five different types of filters that can be applied to a grid query.

    <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/Filter.html>

    Known implementations: RowValidationResultFilter, CellValueFilter,
    RowSelectionFilter, RowIsValidFilter, RowIdFilter.

    The concrete subclass is determined by the concreteType field in the REST response.
    """

    @abstractmethod
    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        ...

synapseclient.models.curation.RowValidationResultFilter dataclass

Bases: Filter, EnumCoercionMixin

Use this filter to find rows that have data quality issues or schema validation errors.

https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/RowValidationResultFilter.html

To find type errors, use the LIKE operator with '%expected type:%' as the validation result value.

ATTRIBUTE DESCRIPTION
operator

The comparison operator.

TYPE: Optional[Union[ValidationOperator, str]]

validation_result_value

A validation result value. For wildcards use '%' to represents zero or more characters, and '_' to represents a single character.

TYPE: Optional[str]

Source code in synapseclient/models/curation.py
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
@dataclass
class RowValidationResultFilter(Filter, EnumCoercionMixin):
    """
    Use this filter to find rows that have data quality issues or schema
    validation errors.

    <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/RowValidationResultFilter.html>

    To find type errors, use the LIKE operator with '%expected type:%' as the
    validation result value.

    Attributes:
        operator: The comparison operator.
        validation_result_value: A validation result value. For wildcards use
            '%' to represents zero or more characters, and '_' to represents a
            single character.
    """

    _ENUM_FIELDS: ClassVar[dict[str, type]] = {"operator": ValidationOperator}

    operator: Optional[Union[ValidationOperator, str]] = None
    """The comparison operator."""

    validation_result_value: Optional[str] = None
    """A validation result value. For wildcards use '%' to represents zero or
    more characters, and '_' to represents a single character."""

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        request_dict = {
            "concreteType": ROW_VALIDATION_RESULT_FILTER,
            "operator": self.operator.value if self.operator is not None else None,
            "validationResultValue": self.validation_result_value,
        }
        delete_none_keys(request_dict)
        return request_dict

Attributes

operator class-attribute instance-attribute

operator: Optional[Union[ValidationOperator, str]] = None

The comparison operator.

validation_result_value class-attribute instance-attribute

validation_result_value: Optional[str] = None

A validation result value. For wildcards use '%' to represents zero or more characters, and '_' to represents a single character.


synapseclient.models.curation.CellValueFilter dataclass

Bases: Filter, EnumCoercionMixin

A filter used to select rows based on cell values. For example, to handle a user request like 'find all rows where the Project column is Alpha', you would set 'columnName' to 'Project', 'operator' to 'EQUALS', and 'value' to ['Alpha'].

https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/CellValueFilter.html

ATTRIBUTE DESCRIPTION
column_name

The name of the column to filter by.

TYPE: Optional[str]

operator

The comparison operator.

TYPE: Optional[Union[CellValueOperator, str]]

value

Use operators like 'EQUALS' or 'LIKE' with the 'value' property for standard comparisons. The 'IS_NULL' operator can be used to find null values. The 'IS_UNDEFINED' operator can be used to find undefined values. When using IN or NOT_IN operators, value should be an array of values to compare against. When using either 'LIKE' or 'NOT_LIKE', the wildcard character '%' is used to represents zero or more characters, and '_' is used to represent a single character.

TYPE: Optional[Any]

Source code in synapseclient/models/curation.py
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
@dataclass
class CellValueFilter(Filter, EnumCoercionMixin):
    """
    A filter used to select rows based on cell values. For example, to handle a
    user request like 'find all rows where the Project column is Alpha', you
    would set 'columnName' to 'Project', 'operator' to 'EQUALS', and 'value' to
    ['Alpha'].

    <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/CellValueFilter.html>

    Attributes:
        column_name: The name of the column to filter by.
        operator: The comparison operator.
        value: Use operators like 'EQUALS' or 'LIKE' with the 'value' property
            for standard comparisons. The 'IS_NULL' operator can be used to
            find null values. The 'IS_UNDEFINED' operator can be used to find
            undefined values. When using IN or NOT_IN operators, value should
            be an array of values to compare against. When using either 'LIKE'
            or 'NOT_LIKE', the wildcard character '%' is used to represents
            zero or more characters, and '_' is used to represent a single
            character.
    """

    _ENUM_FIELDS: ClassVar[dict[str, type]] = {"operator": CellValueOperator}

    column_name: Optional[str] = None
    """The name of the column to filter by."""

    operator: Optional[Union[CellValueOperator, str]] = None
    """The comparison operator."""

    value: Optional[Any] = None
    """Use operators like 'EQUALS' or 'LIKE' with the 'value' property for
    standard comparisons. The 'IS_NULL' operator can be used to find null
    values. The 'IS_UNDEFINED' operator can be used to find undefined values.
    When using IN or NOT_IN operators, value should be an array of values to
    compare against. When using either 'LIKE' or 'NOT_LIKE', the wildcard
    character '%' is used to represents zero or more characters, and '_' is
    used to represent a single character."""

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        request_dict = {
            "concreteType": CELL_VALUE_FILTER,
            "columnName": self.column_name,
            "operator": self.operator.value if self.operator is not None else None,
            "value": self.value,
        }
        delete_none_keys(request_dict)
        return request_dict

Attributes

column_name class-attribute instance-attribute

column_name: Optional[str] = None

The name of the column to filter by.

operator class-attribute instance-attribute

operator: Optional[Union[CellValueOperator, str]] = None

The comparison operator.

value class-attribute instance-attribute

value: Optional[Any] = None

Use operators like 'EQUALS' or 'LIKE' with the 'value' property for standard comparisons. The 'IS_NULL' operator can be used to find null values. The 'IS_UNDEFINED' operator can be used to find undefined values. When using IN or NOT_IN operators, value should be an array of values to compare against. When using either 'LIKE' or 'NOT_LIKE', the wildcard character '%' is used to represents zero or more characters, and '_' is used to represent a single character.


synapseclient.models.curation.RowSelectionFilter dataclass

Bases: Filter

Use this filter to narrow down results based on rows the user has actively selected in the interface. For user requests like 'show me only my selected items' or 'run this analysis on the rows I've checked', set the 'isSelected' property to true.

https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/RowSelectionFilter.html

ATTRIBUTE DESCRIPTION
is_selected

When true, only rows that the user has selected will be returned. When false, rows that the user has selected will be excluded.

TYPE: Optional[bool]

Source code in synapseclient/models/curation.py
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
@dataclass
class RowSelectionFilter(Filter):
    """
    Use this filter to narrow down results based on rows the user has actively
    selected in the interface. For user requests like 'show me only my selected
    items' or 'run this analysis on the rows I've checked', set the
    'isSelected' property to true.

    <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/RowSelectionFilter.html>

    Attributes:
        is_selected: When true, only rows that the user has selected will be
            returned. When false, rows that the user has selected will be
            excluded.
    """

    is_selected: Optional[bool] = None
    """When true, only rows that the user has selected will be returned. When
    false, rows that the user has selected will be excluded."""

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        request_dict = {
            "concreteType": ROW_SELECTION_FILTER,
            "isSelected": self.is_selected,
        }
        delete_none_keys(request_dict)
        return request_dict

Attributes

is_selected class-attribute instance-attribute

is_selected: Optional[bool] = None

When true, only rows that the user has selected will be returned. When false, rows that the user has selected will be excluded.


synapseclient.models.curation.RowIsValidFilter dataclass

Bases: Filter

Use this filter for simple requests to find all 'valid' or 'invalid' rows based on their overall validation status.

https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/RowIsValidFilter.html

ATTRIBUTE DESCRIPTION
value

Set to true to find rows that are valid according to the schema. Set to false to find rows that are invalid and have validation errors.

TYPE: Optional[bool]

Source code in synapseclient/models/curation.py
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
@dataclass
class RowIsValidFilter(Filter):
    """
    Use this filter for simple requests to find all 'valid' or 'invalid' rows
    based on their overall validation status.

    <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/RowIsValidFilter.html>

    Attributes:
        value: Set to true to find rows that are valid according to the
            schema. Set to false to find rows that are invalid and have
            validation errors.
    """

    value: Optional[bool] = None
    """Set to true to find rows that are valid according to the schema. Set to
    false to find rows that are invalid and have validation errors."""

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        request_dict = {"concreteType": ROW_IS_VALID_FILTER, "value": self.value}
        delete_none_keys(request_dict)
        return request_dict

Attributes

value class-attribute instance-attribute

value: Optional[bool] = None

Set to true to find rows that are valid according to the schema. Set to false to find rows that are invalid and have validation errors.


synapseclient.models.curation.RowIdFilter dataclass

Bases: Filter

Row ID inclusion filter. Use when you need to operate on specific existing rows by their explicit row IDs obtained from a prior grid query (e.g., an update). The filter matches any row whose ID is in the provided list (logical OR semantics). Do not use for pattern matching or broad selection; supply only the exact row IDs you intend to modify or retrieve.

https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/RowIdFilter.html

ATTRIBUTE DESCRIPTION
row_ids_in

Array of explicit row IDs. The result will include any row whose ID appears in this list (logical OR). Provide only IDs previously obtained from a grid query. Omit this filter if you do not know the IDs. Do not include duplicates or IDs not present in the current grid.

TYPE: Optional[list[str]]

Source code in synapseclient/models/curation.py
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
@dataclass
class RowIdFilter(Filter):
    """
    Row ID inclusion filter. Use when you need to operate on specific existing
    rows by their explicit row IDs obtained from a prior grid query (e.g., an
    update). The filter matches any row whose ID is in the provided list
    (logical OR semantics). Do not use for pattern matching or broad selection;
    supply only the exact row IDs you intend to modify or retrieve.

    <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/RowIdFilter.html>

    Attributes:
        row_ids_in: Array of explicit row IDs. The result will include any row
            whose ID appears in this list (logical OR). Provide only IDs
            previously obtained from a grid query. Omit this filter if you do
            not know the IDs. Do not include duplicates or IDs not present in
            the current grid.
    """

    row_ids_in: Optional[list[str]] = None
    """Array of explicit row IDs. The result will include any row whose ID
    appears in this list (logical OR). Provide only IDs previously obtained
    from a grid query. Omit this filter if you do not know the IDs. Do not
    include duplicates or IDs not present in the current grid."""

    def to_synapse_request(self) -> Dict[str, Any]:
        """
        Converts this dataclass to a dictionary suitable for a Synapse REST API request.

        Returns:
            A dictionary representation of this object for API requests.
        """
        request_dict = {"concreteType": ROW_ID_FILTER, "rowIdsIn": self.row_ids_in}
        delete_none_keys(request_dict)
        return request_dict

Attributes

row_ids_in class-attribute instance-attribute

row_ids_in: Optional[list[str]] = None

Array of explicit row IDs. The result will include any row whose ID appears in this list (logical OR). Provide only IDs previously obtained from a grid query. Omit this filter if you do not know the IDs. Do not include duplicates or IDs not present in the current grid.


synapseclient.models.curation.ValidationOperator

Bases: str, Enum

The comparison operator.

See https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/ValidationOperator.html.

Source code in synapseclient/models/curation.py
2744
2745
2746
2747
2748
2749
2750
2751
2752
class ValidationOperator(str, Enum):
    """
    The comparison operator.

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

    LIKE = "LIKE"
    NOT_LIKE = "NOT_LIKE"

synapseclient.models.curation.CellValueOperator

Bases: str, Enum

The comparison operator.

See https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/CellValueOperator.html.

Source code in synapseclient/models/curation.py
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
class CellValueOperator(str, Enum):
    """
    The comparison operator.

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

    EQUALS = "EQUALS"
    NOT_EQUALS = "NOT_EQUALS"
    GREATER_THAN = "GREATER_THAN"
    LESS_THAN = "LESS_THAN"
    GREATER_THAN_OR_EQUALS = "GREATER_THAN_OR_EQUALS"
    LESS_THAN_OR_EQUALS = "LESS_THAN_OR_EQUALS"
    IN = "IN"
    NOT_IN = "NOT_IN"
    LIKE = "LIKE"
    NOT_LIKE = "NOT_LIKE"
    IS_NULL = "IS_NULL"
    IS_NOT_NULL = "IS_NOT_NULL"
    IS_UNDEFINED = "IS_UNDEFINED"
    IS_DEFINED = "IS_DEFINED"

synapseclient.models.curation.GridQueryResult dataclass

A single page of rows returned from a grid query.

Represents a Synapse QueryResult.

Note: This is distinct from the SQL-based table QueryResult (org.sagebionetworks.repo.model.table.QueryResult), which wraps SQL query results rather than a grid query's SelectColumn/Row objects.

ATTRIBUTE DESCRIPTION
select_columns

Information about the selected columns.

TYPE: Optional[list[SelectColumn]]

rows

A single page of rows.

TYPE: Optional[list[GridRow]]

Source code in synapseclient/models/curation.py
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
@dataclass
class GridQueryResult:
    """
    A single page of rows returned from a grid query.

    Represents a [Synapse QueryResult](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/result/QueryResult.html).

    Note: This is distinct from the SQL-based table
    [QueryResult](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/table/QueryResult.html)
    (`org.sagebionetworks.repo.model.table.QueryResult`), which wraps SQL query
    results rather than a grid query's SelectColumn/Row objects.

    Attributes:
        select_columns: Information about the selected columns.
        rows: A single page of rows.
    """

    select_columns: Optional[list[SelectColumn]] = None
    """Information about the selected columns."""

    rows: Optional[list[GridRow]] = None
    """A single page of rows."""

    def fill_from_dict(self, synapse_response: Dict[str, Any]) -> "GridQueryResult":
        """
        Converts a response from the REST API into this dataclass.

        Arguments:
            synapse_response: The response from the REST API.

        Returns:
            The GridQueryResult object.
        """
        select_columns_data = synapse_response.get("selectColumns", None)
        self.select_columns = (
            [SelectColumn().fill_from_dict(col) for col in select_columns_data]
            if select_columns_data is not None
            else None
        )

        rows_data = synapse_response.get("rows", None)
        self.rows = (
            [GridRow().fill_from_dict(row) for row in rows_data]
            if rows_data is not None
            else None
        )
        return self

Attributes

select_columns class-attribute instance-attribute

select_columns: Optional[list[SelectColumn]] = None

Information about the selected columns.

rows class-attribute instance-attribute

rows: Optional[list[GridRow]] = None

A single page of rows.


synapseclient.models.curation.GridRow dataclass

A single row of a grid query result.

Represents a Synapse Row.

Note: This is distinct from the SQL-based table Row (org.sagebionetworks.repo.model.table.Row), which represents a row of a table/view query result rather than a grid query result.

ATTRIBUTE DESCRIPTION
row_id

Logical timestamp identifying the row in compact form replicaId.sequenceNumber (e.g. 123.456). Used in filtering operations and update/patch procedures.

TYPE: Optional[str]

data

The JSON object representing a single row.

TYPE: Optional[Dict[str, Any]]

validation_results

Results of validating this row against a JSON schema, if a schema is bound.

TYPE: Optional[GridQueryValidationResult]

Source code in synapseclient/models/curation.py
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
@dataclass
class GridRow:
    """
    A single row of a grid query result.

    Represents a [Synapse Row](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/result/Row.html).

    Note: This is distinct from the SQL-based table
    [Row](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/table/Row.html)
    (`org.sagebionetworks.repo.model.table.Row`), which represents a row of a
    table/view query result rather than a grid query result.

    Attributes:
        row_id: Logical timestamp identifying the row in compact form
            `replicaId.sequenceNumber` (e.g. `123.456`). Used in filtering
            operations and update/patch procedures.
        data: The JSON object representing a single row.
        validation_results: Results of validating this row against a JSON
            schema, if a schema is bound.
    """

    row_id: Optional[str] = None
    """Logical timestamp identifying the row in compact form `replicaId.sequenceNumber`."""

    data: Optional[Dict[str, Any]] = None
    """The JSON object representing a single row."""

    validation_results: Optional[GridQueryValidationResult] = None
    """Results of validating this row against a JSON schema, if a schema is bound."""

    def fill_from_dict(self, synapse_response: Dict[str, Any]) -> "GridRow":
        """
        Converts a response from the REST API into this dataclass.

        Arguments:
            synapse_response: The response from the REST API.

        Returns:
            The GridRow object.
        """
        self.row_id = synapse_response.get("rowId", None)
        self.data = synapse_response.get("data", None)
        validation_results_data = synapse_response.get("validationResults", None)
        self.validation_results = (
            GridQueryValidationResult().fill_from_dict(validation_results_data)
            if validation_results_data is not None
            else None
        )
        return self

Attributes

row_id class-attribute instance-attribute

row_id: Optional[str] = None

Logical timestamp identifying the row in compact form replicaId.sequenceNumber.

data class-attribute instance-attribute

data: Optional[Dict[str, Any]] = None

The JSON object representing a single row.

validation_results class-attribute instance-attribute

validation_results: Optional[GridQueryValidationResult] = None

Results of validating this row against a JSON schema, if a schema is bound.


synapseclient.models.curation.SelectColumn dataclass

Information about a selected column in a grid query result.

Represents a Synapse SelectColumn.

Note: This is distinct from the table SelectColumn (org.sagebionetworks.repo.model.table.SelectColumn), which additionally carries id and columnType. This grid-query SelectColumn only has column_name.

ATTRIBUTE DESCRIPTION
column_name

The name of the column. Will be the alias if one is provided in the select item.

TYPE: Optional[str]

Source code in synapseclient/models/curation.py
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
@dataclass
class SelectColumn:
    """
    Information about a selected column in a grid query result.

    Represents a [Synapse SelectColumn](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/result/SelectColumn.html).

    Note: This is distinct from the table
    [SelectColumn](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/table/SelectColumn.html)
    (`org.sagebionetworks.repo.model.table.SelectColumn`), which additionally carries
    `id` and `columnType`. This grid-query `SelectColumn` only has `column_name`.

    Attributes:
        column_name: The name of the column. Will be the alias if one is
            provided in the select item.
    """

    column_name: Optional[str] = None
    """The name of the column. Will be the alias if one is provided in the select item."""

    def fill_from_dict(self, synapse_response: Dict[str, Any]) -> "SelectColumn":
        """
        Converts a response from the REST API into this dataclass.

        Arguments:
            synapse_response: The response from the REST API.

        Returns:
            The SelectColumn object.
        """
        self.column_name = synapse_response.get("columnName", None)
        return self

Attributes

column_name class-attribute instance-attribute

column_name: Optional[str] = None

The name of the column. Will be the alias if one is provided in the select item.


synapseclient.models.curation.GridQueryValidationResult dataclass

Results of a grid row against a JSON schema

Represents a Synapse ValidationResults.

Note: This is distinct from the general-purpose ValidationResults (org.sagebionetworks.repo.model.schema.ValidationResults), which is used for validating entities/containers against a schema and carries additional fields (objectId, objectType, objectEtag, schema$id, validatedOn, validationException) that this grid-row-specific type does not have.

ATTRIBUTE DESCRIPTION
is_valid

Will be 'true' if the row is valid according to the JSON schema. Will be 'false' when the row is invalid.

TYPE: Optional[bool]

validation_error_message

If the object is not valid according to the schema, a simple one line error message will be provided.

TYPE: Optional[str]

all_validation_messages

If the object is not valid according to the schema, a the flat list of error messages will be provided with one error message per sub-schema. Included only if includeValidationMessages was set to true in the query. Otherwise, this array is omitted to optimize performance.

TYPE: Optional[list[str]]

Source code in synapseclient/models/curation.py
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
@dataclass
class GridQueryValidationResult:
    """
    Results of a grid row against a JSON schema

    Represents a [Synapse ValidationResults](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/grid/query/result/ValidationResults.html).

    Note: This is distinct from the general-purpose
    [ValidationResults](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/schema/ValidationResults.html)
    (`org.sagebionetworks.repo.model.schema.ValidationResults`), which is used
    for validating entities/containers against a schema and carries additional
    fields (`objectId`, `objectType`, `objectEtag`, `schema$id`, `validatedOn`,
    `validationException`) that this grid-row-specific type does not have.

    Attributes:
        is_valid: Will be 'true' if the row is valid according to the JSON
            schema. Will be 'false' when the row is invalid.
        validation_error_message: If the object is not valid according to the
            schema, a simple one line error message will be provided.
        all_validation_messages: If the object is not valid according to the
            schema, a the flat list of error messages will be provided with one
            error message per sub-schema. Included only if
            includeValidationMessages was set to true in the query. Otherwise,
            this array is omitted to optimize performance.
    """

    is_valid: Optional[bool] = None
    """Will be 'true' if the row is valid according to the JSON schema. Will be
    'false' when the row is invalid."""

    validation_error_message: Optional[str] = None
    """If the object is not valid according to the schema, a simple one line
    error message will be provided."""

    all_validation_messages: Optional[list[str]] = None
    """If the object is not valid according to the schema, a the flat list of
    error messages will be provided with one error message per sub-schema.
    Included only if includeValidationMessages was set to true in the query.
    Otherwise, this array is omitted to optimize performance."""

    def fill_from_dict(
        self, synapse_response: Dict[str, Any]
    ) -> "GridQueryValidationResult":
        """
        Converts a response from the REST API into this dataclass.

        Arguments:
            synapse_response: The response from the REST API.

        Returns:
            The GridQueryValidationResult object.
        """
        self.is_valid = synapse_response.get("isValid", None)
        self.validation_error_message = synapse_response.get(
            "validationErrorMessage", None
        )
        self.all_validation_messages = synapse_response.get(
            "allValidationMessages", None
        )
        return self

Attributes

is_valid class-attribute instance-attribute

is_valid: Optional[bool] = None

Will be 'true' if the row is valid according to the JSON schema. Will be 'false' when the row is invalid.

validation_error_message class-attribute instance-attribute

validation_error_message: Optional[str] = None

If the object is not valid according to the schema, a simple one line error message will be provided.

all_validation_messages class-attribute instance-attribute

all_validation_messages: Optional[list[str]] = None

If the object is not valid according to the schema, a the flat list of error messages will be provided with one error message per sub-schema. Included only if includeValidationMessages was set to true in the query. Otherwise, this array is omitted to optimize performance.