1010
1111from .core ._auth import _AuthManager
1212from .core .config import DataverseConfig
13+ from .core .results import FluentResult , RequestMetadata
1314from .data ._odata import _ODataClient
1415
1516
@@ -108,7 +109,9 @@ def _scoped_odata(self) -> Iterator[_ODataClient]:
108109 yield od
109110
110111 # ---------------- Unified CRUD: create/update/delete ----------------
111- def create (self , table_schema_name : str , records : Union [Dict [str , Any ], List [Dict [str , Any ]]]) -> List [str ]:
112+ def create (
113+ self , table_schema_name : str , records : Union [Dict [str , Any ], List [Dict [str , Any ]]]
114+ ) -> FluentResult [List [str ]]:
112115 """
113116 Create one or more records by table name.
114117
@@ -118,8 +121,9 @@ def create(self, table_schema_name: str, records: Union[Dict[str, Any], List[Dic
118121 Each dictionary should contain column schema names as keys.
119122 :type records: :class:`dict` or :class:`list` of :class:`dict`
120123
121- :return: List of created record GUIDs. Returns a single-element list for a single input.
122- :rtype: :class:`list` of :class:`str`
124+ :return: FluentResult wrapping list of created record GUIDs. Acts like a list
125+ by default. Call ``.with_detail_response()`` for telemetry.
126+ :rtype: :class:`~PowerPlatform.Dataverse.core.results.FluentResult` of :class:`list` of :class:`str`
123127
124128 :raises TypeError: If ``records`` is not a dict or list[dict], or if the internal
125129 client returns an unexpected type.
@@ -139,25 +143,31 @@ def create(self, table_schema_name: str, records: Union[Dict[str, Any], List[Dic
139143 ]
140144 ids = client.create("account", records)
141145 print(f"Created {len(ids)} accounts")
146+
147+ Access telemetry with ``.with_detail_response()``::
148+
149+ response = client.create("account", {"name": "Test"}).with_detail_response()
150+ print(f"Timing: {response.telemetry['timing_ms']}ms")
142151 """
143152 with self ._scoped_odata () as od :
144153 entity_set = od ._entity_set_from_schema_name (table_schema_name )
145154 if isinstance (records , dict ):
146- rid = od ._create (entity_set , table_schema_name , records )
147- # _create returns str on single input
155+ rid , metadata = od ._create_with_metadata (entity_set , table_schema_name , records )
148156 if not isinstance (rid , str ):
149157 raise TypeError ("_create (single) did not return GUID string" )
150- return [rid ]
158+ return FluentResult ( [rid ], metadata , batch_info = { "total" : 1 , "success" : 1 , "failures" : 0 })
151159 if isinstance (records , list ):
152- ids = od ._create_multiple (entity_set , table_schema_name , records )
160+ ids , metadata , batch_info = od ._create_multiple_with_metadata (
161+ entity_set , table_schema_name , records
162+ )
153163 if not isinstance (ids , list ) or not all (isinstance (x , str ) for x in ids ):
154164 raise TypeError ("_create (multi) did not return list[str]" )
155- return ids
165+ return FluentResult ( ids , metadata , batch_info = batch_info )
156166 raise TypeError ("records must be dict or list[dict]" )
157167
158168 def update (
159169 self , table_schema_name : str , ids : Union [str , List [str ]], changes : Union [Dict [str , Any ], List [Dict [str , Any ]]]
160- ) -> None :
170+ ) -> FluentResult [ None ] :
161171 """
162172 Update one or more records.
163173
@@ -177,6 +187,9 @@ def update(
177187 have equal length for one-to-one mapping.
178188 :type changes: :class:`dict` or :class:`list` of :class:`dict`
179189
190+ :return: FluentResult wrapping None. Call ``.with_detail_response()`` for telemetry.
191+ :rtype: :class:`~PowerPlatform.Dataverse.core.results.FluentResult` of ``None``
192+
180193 :raises TypeError: If ``ids`` is not str or list[str], or if ``changes`` type doesn't match usage pattern.
181194
182195 .. note::
@@ -199,24 +212,34 @@ def update(
199212 {"name": "Updated Name 2"}
200213 ]
201214 client.update("account", ids, changes)
215+
216+ Access telemetry with ``.with_detail_response()``::
217+
218+ response = client.update("account", id, {"name": "Test"}).with_detail_response()
219+ print(f"Timing: {response.telemetry['timing_ms']}ms")
202220 """
203221 with self ._scoped_odata () as od :
204222 if isinstance (ids , str ):
205223 if not isinstance (changes , dict ):
206224 raise TypeError ("For single id, changes must be a dict" )
207- od ._update (table_schema_name , ids , changes ) # discard representation
208- return None
225+ _ , metadata = od ._update_with_metadata (table_schema_name , ids , changes )
226+ return FluentResult ( None , metadata )
209227 if not isinstance (ids , list ):
210228 raise TypeError ("ids must be str or list[str]" )
229+ # For bulk updates, we still use the original method as _update_by_ids doesn't have a _with_metadata variant yet
230+ # TODO: Add _update_by_ids_with_metadata for bulk update telemetry
211231 od ._update_by_ids (table_schema_name , ids , changes )
212- return None
232+ # Create placeholder metadata for bulk updates
233+ placeholder_metadata = RequestMetadata ()
234+ num_updates = len (ids )
235+ return FluentResult (None , placeholder_metadata , batch_info = {"total" : num_updates , "success" : num_updates , "failures" : 0 })
213236
214237 def delete (
215238 self ,
216239 table_schema_name : str ,
217240 ids : Union [str , List [str ]],
218241 use_bulk_delete : bool = True ,
219- ) -> Optional [str ]:
242+ ) -> FluentResult [ Optional [str ] ]:
220243 """
221244 Delete one or more records by GUID.
222245
@@ -228,12 +251,13 @@ def delete(
228251 return its async job identifier. When ``False`` each record is deleted sequentially.
229252 :type use_bulk_delete: :class:`bool`
230253
254+ :return: FluentResult wrapping BulkDelete job ID (for bulk) or None (for single).
255+ Call ``.with_detail_response()`` for telemetry.
256+ :rtype: :class:`~PowerPlatform.Dataverse.core.results.FluentResult` of :class:`str` or ``None``
257+
231258 :raises TypeError: If ``ids`` is not str or list[str].
232259 :raises HttpError: If the underlying Web API delete request fails.
233260
234- :return: BulkDelete job ID when deleting multiple records via BulkDelete; otherwise ``None``.
235- :rtype: :class:`str` or None
236-
237261 Example:
238262 Delete a single record::
239263
@@ -242,22 +266,31 @@ def delete(
242266 Delete multiple records::
243267
244268 job_id = client.delete("account", [id1, id2, id3])
269+
270+ Access telemetry with ``.with_detail_response()``::
271+
272+ response = client.delete("account", account_id).with_detail_response()
273+ print(f"Timing: {response.telemetry['timing_ms']}ms")
245274 """
246275 with self ._scoped_odata () as od :
247276 if isinstance (ids , str ):
248- od ._delete (table_schema_name , ids )
249- return None
277+ _ , metadata = od ._delete_with_metadata (table_schema_name , ids )
278+ return FluentResult ( None , metadata )
250279 if not isinstance (ids , list ):
251280 raise TypeError ("ids must be str or list[str]" )
252281 if not ids :
253- return None
282+ return FluentResult ( None , RequestMetadata ())
254283 if not all (isinstance (rid , str ) for rid in ids ):
255284 raise TypeError ("ids must contain string GUIDs" )
256285 if use_bulk_delete :
257- return od ._delete_multiple (table_schema_name , ids )
286+ # TODO: Add _delete_multiple_with_metadata for bulk delete telemetry
287+ job_id = od ._delete_multiple (table_schema_name , ids )
288+ return FluentResult (job_id , RequestMetadata (), batch_info = {"total" : len (ids )})
289+ # Sequential deletes
290+ last_metadata = RequestMetadata ()
258291 for rid in ids :
259- od ._delete (table_schema_name , rid )
260- return None
292+ _ , last_metadata = od ._delete_with_metadata (table_schema_name , rid )
293+ return FluentResult ( None , last_metadata , batch_info = { "total" : len ( ids ), "success" : len ( ids ), "failures" : 0 })
261294
262295 def get (
263296 self ,
@@ -269,11 +302,11 @@ def get(
269302 top : Optional [int ] = None ,
270303 expand : Optional [List [str ]] = None ,
271304 page_size : Optional [int ] = None ,
272- ) -> Union [Dict [str , Any ], Iterable [List [Dict [str , Any ]]]]:
305+ ) -> Union [FluentResult [ Dict [str , Any ] ], Iterable [List [Dict [str , Any ]]]]:
273306 """
274307 Fetch a single record by ID or query multiple records.
275308
276- When ``record_id`` is provided, returns a single record dictionary.
309+ When ``record_id`` is provided, returns a FluentResult wrapping the record dictionary.
277310 When ``record_id`` is None, returns a generator yielding batches of records.
278311
279312 :param table_schema_name: Schema name of the table (e.g. ``"account"`` or ``"new_MyTestTable"``).
@@ -293,9 +326,11 @@ def get(
293326 :param page_size: Optional number of records per page for pagination.
294327 :type page_size: :class:`int` or None
295328
296- :return: Single record dict if ``record_id`` is provided, otherwise a generator
329+ :return: FluentResult wrapping single record dict if ``record_id`` is provided
330+ (call ``.with_detail_response()`` for telemetry), otherwise a generator
297331 yielding lists of record dictionaries (one list per page).
298- :rtype: :class:`dict` or :class:`collections.abc.Iterable` of :class:`list` of :class:`dict`
332+ :rtype: :class:`~PowerPlatform.Dataverse.core.results.FluentResult` of :class:`dict`
333+ or :class:`collections.abc.Iterable` of :class:`list` of :class:`dict`
299334
300335 :raises TypeError: If ``record_id`` is provided but not a string.
301336
@@ -305,6 +340,11 @@ def get(
305340 record = client.get("account", record_id=account_id, select=["name", "telephone1"])
306341 print(record["name"])
307342
343+ Access telemetry for single record fetch::
344+
345+ response = client.get("account", record_id=account_id).with_detail_response()
346+ print(f"Timing: {response.telemetry['timing_ms']}ms")
347+
308348 Query multiple records with filtering (note: exact logical names in filter)::
309349
310350 for batch in client.get(
@@ -340,11 +380,12 @@ def get(
340380 if not isinstance (record_id , str ):
341381 raise TypeError ("record_id must be str" )
342382 with self ._scoped_odata () as od :
343- return od ._get (
383+ record , metadata = od ._get_with_metadata (
344384 table_schema_name ,
345385 record_id ,
346386 select = select ,
347387 )
388+ return FluentResult (record , metadata )
348389
349390 def _paged () -> Iterable [List [Dict [str , Any ]]]:
350391 with self ._scoped_odata () as od :
0 commit comments