Skip to content

Commit 96c7127

Browse files
committed
used updated claude me files
1 parent f54c099 commit 96c7127

File tree

5 files changed

+447
-0
lines changed

5 files changed

+447
-0
lines changed

synapseclient/core/constants/concrete_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
GRID_RECORD_SET_EXPORT_REQUEST = (
123123
"org.sagebionetworks.repo.model.grid.GridRecordSetExportRequest"
124124
)
125+
GRID_CSV_IMPORT_REQUEST = "org.sagebionetworks.repo.model.grid.GridCsvImportRequest"
125126
LIST_GRID_SESSIONS_REQUEST = (
126127
"org.sagebionetworks.repo.model.grid.ListGridSessionsRequest"
127128
)

synapseclient/models/curation.py

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
data or metadata in Synapse.
66
"""
77

8+
import os
89
from dataclasses import dataclass, field, replace
910
from typing import Any, AsyncGenerator, Dict, Generator, Optional, Protocol, Union
1011

@@ -28,6 +29,7 @@
2829
from synapseclient.core.constants.concrete_types import (
2930
CREATE_GRID_REQUEST,
3031
FILE_BASED_METADATA_TASK_PROPERTIES,
32+
GRID_CSV_IMPORT_REQUEST,
3133
GRID_RECORD_SET_EXPORT_REQUEST,
3234
LIST_GRID_SESSIONS_REQUEST,
3335
LIST_GRID_SESSIONS_RESPONSE,
@@ -1078,6 +1080,64 @@ def to_synapse_request(self) -> Dict[str, Any]:
10781080
return request_dict
10791081

10801082

1083+
@dataclass
1084+
class GridCsvImportRequest(AsynchronousCommunicator):
1085+
"""
1086+
Request to import a CSV file into an existing grid session.
1087+
1088+
Represents a Synapse GridCsvImportRequest:
1089+
POST /grid/import/csv/async/start
1090+
GET /grid/import/csv/async/get/{asyncToken}
1091+
1092+
Attributes:
1093+
concrete_type: The concrete type for the request
1094+
session_id: The grid session ID to import the CSV into
1095+
file_handle_id: The file handle ID of the CSV file to import
1096+
response_session_id: The session ID from the import response (populated from response)
1097+
"""
1098+
1099+
concrete_type: str = GRID_CSV_IMPORT_REQUEST
1100+
"""The concrete type for the request"""
1101+
1102+
session_id: Optional[str] = None
1103+
"""The grid session ID to import the CSV into"""
1104+
1105+
file_handle_id: Optional[str] = None
1106+
"""The file handle ID of the CSV file to import"""
1107+
1108+
response_session_id: Optional[str] = None
1109+
"""The session ID from the import response (populated from response)"""
1110+
1111+
def fill_from_dict(
1112+
self, synapse_response: Dict[str, Any]
1113+
) -> "GridCsvImportRequest":
1114+
"""
1115+
Converts a response from the REST API into this dataclass.
1116+
1117+
Arguments:
1118+
synapse_response: The response from the REST API.
1119+
1120+
Returns:
1121+
The GridCsvImportRequest object.
1122+
"""
1123+
self.response_session_id = synapse_response.get("sessionId", None)
1124+
return self
1125+
1126+
def to_synapse_request(self) -> Dict[str, Any]:
1127+
"""
1128+
Converts this dataclass to a dictionary suitable for a Synapse REST API request.
1129+
1130+
Returns:
1131+
A dictionary representation of this object for API requests.
1132+
"""
1133+
request_dict: Dict[str, Any] = {"concreteType": self.concrete_type}
1134+
if self.session_id is not None:
1135+
request_dict["sessionId"] = self.session_id
1136+
if self.file_handle_id is not None:
1137+
request_dict["fileHandleId"] = self.file_handle_id
1138+
return request_dict
1139+
1140+
10811141
@dataclass
10821142
class GridSession:
10831143
"""
@@ -1373,6 +1433,69 @@ def delete(self, *, synapse_client: Optional[Synapse] = None) -> None:
13731433
"""
13741434
return None
13751435

1436+
def import_csv(
1437+
self,
1438+
file_handle_id: Optional[str] = None,
1439+
path: Optional[str] = None,
1440+
*,
1441+
timeout: int = 120,
1442+
synapse_client: Optional[Synapse] = None,
1443+
) -> "Grid":
1444+
"""
1445+
Import a CSV file into the grid session.
1446+
1447+
Accepts either a pre-uploaded `file_handle_id` or a local `path` that will
1448+
be uploaded automatically before importing.
1449+
1450+
Arguments:
1451+
file_handle_id: The Synapse file handle ID of the CSV to import.
1452+
Mutually exclusive with `path`.
1453+
path: Local path to a CSV file. If provided, the file will be uploaded
1454+
via multipart upload and the resulting file handle ID used for import.
1455+
Mutually exclusive with `file_handle_id`.
1456+
timeout: The number of seconds to wait for the async job to complete.
1457+
Defaults to 120.
1458+
synapse_client: If not passed in and caching was not disabled by
1459+
`Synapse.allow_client_caching(False)` this will use the last created
1460+
instance from the Synapse class constructor.
1461+
1462+
Returns:
1463+
The Grid object (self).
1464+
1465+
Raises:
1466+
ValueError: If `session_id` is not set.
1467+
ValueError: If neither `file_handle_id` nor `path` is provided.
1468+
1469+
Example: Import CSV by file handle ID
1470+
 
1471+
1472+
```python
1473+
from synapseclient import Synapse
1474+
from synapseclient.models import Grid
1475+
1476+
syn = Synapse()
1477+
syn.login()
1478+
1479+
grid = Grid(session_id="abc-123-def")
1480+
grid = grid.import_csv(file_handle_id="12345678")
1481+
```
1482+
1483+
Example: Import CSV from a local path
1484+
 
1485+
1486+
```python
1487+
from synapseclient import Synapse
1488+
from synapseclient.models import Grid
1489+
1490+
syn = Synapse()
1491+
syn.login()
1492+
1493+
grid = Grid(session_id="abc-123-def")
1494+
grid = grid.import_csv(path="/path/to/data.csv")
1495+
```
1496+
"""
1497+
return self
1498+
13761499
@classmethod
13771500
def list(
13781501
cls,
@@ -1838,3 +1961,106 @@ async def main():
18381961
await delete_grid_session(
18391962
session_id=self.session_id, synapse_client=synapse_client
18401963
)
1964+
1965+
async def import_csv_async(
1966+
self,
1967+
file_handle_id: Optional[str] = None,
1968+
path: Optional[str] = None,
1969+
*,
1970+
timeout: int = 120,
1971+
synapse_client: Optional[Synapse] = None,
1972+
) -> "Grid":
1973+
"""
1974+
Import a CSV file into the grid session.
1975+
1976+
Accepts either a pre-uploaded `file_handle_id` or a local `path` that will
1977+
be uploaded automatically before importing.
1978+
1979+
Arguments:
1980+
file_handle_id: The Synapse file handle ID of the CSV to import.
1981+
Mutually exclusive with `path`.
1982+
path: Local path to a CSV file. If provided, the file will be uploaded
1983+
via multipart upload and the resulting file handle ID used for import.
1984+
Mutually exclusive with `file_handle_id`.
1985+
timeout: The number of seconds to wait for the async job to complete.
1986+
Defaults to 120.
1987+
synapse_client: If not passed in and caching was not disabled by
1988+
`Synapse.allow_client_caching(False)` this will use the last created
1989+
instance from the Synapse class constructor.
1990+
1991+
Returns:
1992+
The Grid object (self).
1993+
1994+
Raises:
1995+
ValueError: If `session_id` is not set.
1996+
ValueError: If neither `file_handle_id` nor `path` is provided.
1997+
1998+
Example: Import CSV by file handle ID asynchronously
1999+
 
2000+
2001+
```python
2002+
import asyncio
2003+
from synapseclient import Synapse
2004+
from synapseclient.models import Grid
2005+
2006+
syn = Synapse()
2007+
syn.login()
2008+
2009+
async def main():
2010+
grid = Grid(session_id="abc-123-def")
2011+
grid = await grid.import_csv_async(file_handle_id="12345678")
2012+
2013+
asyncio.run(main())
2014+
```
2015+
2016+
Example: Import CSV from a local path asynchronously
2017+
 
2018+
2019+
```python
2020+
import asyncio
2021+
from synapseclient import Synapse
2022+
from synapseclient.models import Grid
2023+
2024+
syn = Synapse()
2025+
syn.login()
2026+
2027+
async def main():
2028+
grid = Grid(session_id="abc-123-def")
2029+
grid = await grid.import_csv_async(path="/path/to/data.csv")
2030+
2031+
asyncio.run(main())
2032+
```
2033+
"""
2034+
if not self.session_id:
2035+
raise ValueError("session_id is required to import CSV into a GridSession")
2036+
if not file_handle_id and not path:
2037+
raise ValueError(
2038+
"Either file_handle_id or path must be provided to import a CSV"
2039+
)
2040+
2041+
trace.get_current_span().set_attributes(
2042+
{
2043+
"synapse.session_id": self.session_id or "",
2044+
}
2045+
)
2046+
2047+
client = Synapse.get_client(synapse_client=synapse_client)
2048+
2049+
if path:
2050+
from synapseclient.core.upload.multipart_upload_async import (
2051+
multipart_upload_file_async,
2052+
)
2053+
2054+
path = os.path.expanduser(path)
2055+
file_handle_id = await multipart_upload_file_async(
2056+
syn=client, file_path=path, content_type="text/csv"
2057+
)
2058+
2059+
import_request = GridCsvImportRequest(
2060+
session_id=self.session_id, file_handle_id=file_handle_id
2061+
)
2062+
await import_request.send_job_and_wait_async(
2063+
timeout=timeout, synapse_client=client
2064+
)
2065+
2066+
return self

synapseclient/models/mixins/asynchronous_job.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
CREATE_GRID_REQUEST,
1616
CREATE_SCHEMA_REQUEST,
1717
GET_VALIDATION_SCHEMA_REQUEST,
18+
GRID_CSV_IMPORT_REQUEST,
1819
GRID_RECORD_SET_EXPORT_REQUEST,
1920
QUERY_BUNDLE_REQUEST,
2021
QUERY_TABLE_CSV_REQUEST,
@@ -29,6 +30,7 @@
2930
ASYNC_JOB_URIS = {
3031
AGENT_CHAT_REQUEST: "/agent/chat/async",
3132
CREATE_GRID_REQUEST: "/grid/session/async",
33+
GRID_CSV_IMPORT_REQUEST: "/grid/import/csv/async",
3234
GRID_RECORD_SET_EXPORT_REQUEST: "/grid/export/recordset/async",
3335
TABLE_UPDATE_TRANSACTION_REQUEST: "/entity/{entityId}/table/transaction/async",
3436
GET_VALIDATION_SCHEMA_REQUEST: "/schema/type/validation/async",

tests/integration/synapseclient/models/async/test_grid_async.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,81 @@ async def test_delete_grid_session_validation_error_async(self) -> None:
183183
match="session_id is required to delete a GridSession",
184184
):
185185
await grid.delete_async(synapse_client=self.syn)
186+
187+
async def test_import_csv_async_with_file_handle_id(
188+
self, record_set_fixture: RecordSet
189+
) -> None:
190+
# GIVEN: A grid session and the file handle ID from the record set
191+
grid = Grid(record_set_id=record_set_fixture.id)
192+
created_grid = await grid.create_async(
193+
timeout=ASYNC_JOB_TIMEOUT_SEC, synapse_client=self.syn
194+
)
195+
self.schedule_for_cleanup(created_grid.session_id)
196+
197+
assert created_grid.session_id is not None
198+
assert record_set_fixture.data_file_handle_id is not None
199+
200+
# WHEN: Importing the CSV using the record set's file handle ID
201+
result = await created_grid.import_csv_async(
202+
file_handle_id=record_set_fixture.data_file_handle_id,
203+
timeout=ASYNC_JOB_TIMEOUT_SEC,
204+
synapse_client=self.syn,
205+
)
206+
207+
# THEN: The import should complete without error and return the same grid
208+
assert result is created_grid
209+
210+
async def test_import_csv_async_with_local_path(
211+
self, record_set_fixture: RecordSet
212+
) -> None:
213+
# GIVEN: A grid session and a local CSV file
214+
grid = Grid(record_set_id=record_set_fixture.id)
215+
created_grid = await grid.create_async(
216+
timeout=ASYNC_JOB_TIMEOUT_SEC, synapse_client=self.syn
217+
)
218+
self.schedule_for_cleanup(created_grid.session_id)
219+
220+
assert created_grid.session_id is not None
221+
222+
# Create a local CSV with matching columns
223+
import_data = pd.DataFrame(
224+
{
225+
"id": [10, 20],
226+
"name": ["Zeta", "Eta"],
227+
"value": [99.9, 88.8],
228+
"category": ["D", "E"],
229+
"active": [True, False],
230+
}
231+
)
232+
temp_fd, filename = tempfile.mkstemp(suffix=".csv")
233+
try:
234+
os.close(temp_fd)
235+
import_data.to_csv(filename, index=False)
236+
self.schedule_for_cleanup(filename)
237+
238+
# WHEN: Importing the CSV from a local path
239+
result = await created_grid.import_csv_async(
240+
path=filename,
241+
timeout=ASYNC_JOB_TIMEOUT_SEC,
242+
synapse_client=self.syn,
243+
)
244+
245+
# THEN: The import should complete without error and return the same grid
246+
assert result is created_grid
247+
except Exception:
248+
if os.path.exists(filename):
249+
os.unlink(filename)
250+
raise
251+
252+
async def test_import_csv_async_validation_error_no_session(self) -> None:
253+
# GIVEN: A Grid instance with no session_id
254+
grid = Grid()
255+
256+
# WHEN/THEN: Importing CSV without session_id should raise ValueError
257+
with pytest.raises(
258+
ValueError,
259+
match="session_id is required to import CSV",
260+
):
261+
await grid.import_csv_async(
262+
file_handle_id="12345678", synapse_client=self.syn
263+
)

0 commit comments

Comments
 (0)