2323 BinaryID ,
2424 BoundingBoxLabelsByFilterRequest ,
2525 BoundingBoxLabelsByFilterResponse ,
26+ CaptureInterval ,
2627 CaptureMetadata ,
2728 ConfigureDatabaseUserRequest ,
2829 DataRequest ,
3334 DeleteBinaryDataByIDsResponse ,
3435 DeleteTabularDataRequest ,
3536 DeleteTabularDataResponse ,
37+ ExportTabularDataRequest ,
38+ ExportTabularDataResponse ,
3639 Filter ,
3740 GetDatabaseConnectionRequest ,
3841 GetDatabaseConnectionResponse ,
@@ -145,6 +148,69 @@ def __eq__(self, other: object) -> bool:
145148 return str (self ) == str (other )
146149 return False
147150
151+ @dataclass
152+ class TabularDataPoint :
153+ """Represents a tabular data point and its associated metadata."""
154+
155+ part_id : str
156+ """The robot part ID"""
157+
158+ resource_name : str
159+ """The resource name"""
160+
161+ resource_subtype : str
162+ """The resource subtype. Ex: `rdk:component:sensor`"""
163+
164+ method_name : str
165+ """The method used for data capture. Ex" `Readings`"""
166+
167+ time_captured : datetime
168+ """The time at which the data point was captured"""
169+
170+ organization_id : str
171+ """The organization ID"""
172+
173+ location_id : str
174+ """The location ID"""
175+
176+ robot_name : str
177+ """The robot name"""
178+
179+ robot_id : str
180+ """The robot ID"""
181+
182+ part_name : str
183+ """The robot part name"""
184+
185+ method_parameters : Mapping [str , ValueTypes ]
186+ """Additional parameters associated with the data capture method"""
187+
188+ tags : List [str ]
189+ """A list of tags associated with the data point"""
190+
191+ payload : Mapping [str , ValueTypes ]
192+ """The captured data"""
193+
194+ def __str__ (self ) -> str :
195+ return (
196+ f"TabularDataPoint("
197+ f"robot='{ self .robot_name } ' (id={ self .robot_id } ), "
198+ f"part='{ self .part_name } ' (id={ self .part_id } ), "
199+ f"resource='{ self .resource_name } ' ({ self .resource_subtype } ), "
200+ f"method='{ self .method_name } ', "
201+ f"org={ self .organization_id } , "
202+ f"location={ self .location_id } , "
203+ f"time='{ self .time_captured .isoformat ()} ', "
204+ f"params={ self .method_parameters } , "
205+ f"tags={ self .tags } , "
206+ f"payload={ self .payload } )"
207+ )
208+
209+ def __eq__ (self , other : object ) -> bool :
210+ if isinstance (other , DataClient .TabularDataPoint ):
211+ return str (self ) == str (other )
212+ return False
213+
148214 def __init__ (self , channel : Channel , metadata : Mapping [str , str ]):
149215 """Create a `DataClient` that maintains a connection to app.
150216
@@ -254,7 +320,6 @@ async def tabular_data_by_sql(self, organization_id: str, sql_query: str) -> Lis
254320 sql_query="SELECT * FROM readings LIMIT 5"
255321 )
256322
257-
258323 Args:
259324 organization_id (str): The ID of the organization that owns the data.
260325 You can obtain your organization ID from the Viam app's organization settings page.
@@ -284,7 +349,6 @@ async def tabular_data_by_mql(self, organization_id: str, mql_binary: List[bytes
284349
285350 print(f"Tabular Data: {tabular_data}")
286351
287-
288352 Args:
289353 organization_id (str): The ID of the organization that owns the data.
290354 You can obtain your organization ID from the Viam app's organization settings page.
@@ -307,13 +371,12 @@ async def get_latest_tabular_data(
307371
308372 ::
309373
310- time_captured, time_synced, payload = await data_client.get_latest_tabular_data(
311- part_id="<PART-ID>",
312- resource_name="<RESOURCE-NAME>",
313- resource_subtype="<RESOURCE-SUBTYPE>",
314- method_name="<METHOD-NAME>"
315- )
316-
374+ time_captured, time_synced, payload = await data_client.get_latest_tabular_data(
375+ part_id="<PART-ID>",
376+ resource_name="<RESOURCE-NAME>",
377+ resource_subtype="<RESOURCE-SUBTYPE>",
378+ method_name="<METHOD-NAME>"
379+ )
317380
318381 Args:
319382 part_id (str): The ID of the part that owns the data.
@@ -327,6 +390,7 @@ async def get_latest_tabular_data(
327390 datetime: The time captured,
328391 datetime: The time synced,
329392 Dict[str, ValueTypes]: The latest tabular data captured from the specified data source.
393+
330394 For more information, see `Data Client API <https://docs.viam.com/appendix/apis/data-client/>`_.
331395 """
332396
@@ -338,6 +402,64 @@ async def get_latest_tabular_data(
338402 return None
339403 return response .time_captured .ToDatetime (), response .time_synced .ToDatetime (), struct_to_dict (response .payload )
340404
405+ async def export_tabular_data (
406+ self , part_id : str , resource_name : str , resource_subtype : str , method_name : str , start_time : Optional [datetime ] = None , end_time : Optional [datetime ] = None
407+ ) -> List [TabularDataPoint ]:
408+ """Obtain unified tabular data and metadata from the specified data source.
409+
410+ ::
411+
412+ tabular_data = await data_client.export_tabular_data(
413+ part_id="<PART-ID>",
414+ resource_name="<RESOURCE-NAME>",
415+ resource_subtype="<RESOURCE-SUBTYPE>",
416+ method_name="<METHOD-NAME>",
417+ start_time="<START_TIME>"
418+ end_time="<END_TIME>"
419+ )
420+
421+ print(f"My data: {tabular_data}")
422+
423+ Args:
424+ part_id (str): The ID of the part that owns the data.
425+ resource_name (str): The name of the requested resource that captured the data.
426+ resource_subtype (str): The subtype of the requested resource that captured the data.
427+ method_name (str): The data capture method name.
428+ start_time (datetime): Optional start time for requesting a specific range of data.
429+ end_time (datetime): Optional end time for requesting a specific range of data.
430+
431+ Returns:
432+ List[TabularDataPoint]: The unified tabular data and metadata.
433+
434+ For more information, see `Data Client API <https://docs.viam.com/appendix/apis/data-client/>`_.
435+ """
436+
437+ interval = CaptureInterval (start = datetime_to_timestamp (start_time ), end = datetime_to_timestamp (end_time ))
438+ request = ExportTabularDataRequest (
439+ part_id = part_id , resource_name = resource_name , resource_subtype = resource_subtype , method_name = method_name , interval = interval
440+ )
441+ response : List [ExportTabularDataResponse ] = await self ._data_client .ExportTabularData (request , metadata = self ._metadata )
442+
443+ return [
444+ DataClient .TabularDataPoint (
445+ part_id = resp .part_id ,
446+ resource_name = resp .resource_name ,
447+ resource_subtype = resp .resource_subtype ,
448+ method_name = resp .method_name ,
449+ time_captured = resp .time_captured .ToDatetime (),
450+ organization_id = resp .organization_id ,
451+ location_id = resp .location_id ,
452+ robot_name = resp .robot_name ,
453+ robot_id = resp .robot_id ,
454+ part_name = resp .part_name ,
455+ method_parameters = struct_to_dict (resp .method_parameters ),
456+ tags = list (resp .tags ),
457+ payload = struct_to_dict (resp .payload )
458+ )
459+ for resp in response
460+ ]
461+
462+
341463 async def binary_data_by_filter (
342464 self ,
343465 filter : Optional [Filter ] = None ,
0 commit comments