@@ -6,7 +6,7 @@ from logfire._internal.config import get_base_url_from_token as get_base_url_fro
66from pydantic_evals import Case , Dataset
77from pydantic_evals .evaluators import Evaluator
88from types import TracebackType
9- from typing import Any , Generic , TypeVar , overload
9+ from typing import Any , Generic , Literal , TypeVar , overload
1010from typing_extensions import Self
1111
1212Case = Any
@@ -42,8 +42,9 @@ class LogfireAPIClient(_BaseLogfireAPIClient[Client]):
4242 Example usage:
4343 ```python skip-run="true" skip-reason="external-connection"
4444 from dataclasses import dataclass
45+ from pydantic_evals import Case, Dataset
46+
4547 from logfire.experimental.api_client import LogfireAPIClient
46- from pydantic_evals import Case
4748
4849
4950 @dataclass
@@ -56,21 +57,17 @@ class LogfireAPIClient(_BaseLogfireAPIClient[Client]):
5657 answer: str
5758
5859
59- with LogfireAPIClient(api_key=\' your-api-key\' ) as client:
60- # Create typed dataset
61- dataset = client.create_dataset(
62- name=\' qa-dataset\' ,
63- input_type=MyInput,
64- output_type=MyOutput,
65- )
60+ local_dataset = Dataset[MyInput, MyOutput, None](
61+ name=\' qa-dataset\' ,
62+ cases=[
63+ Case(name=\' q1\' , inputs=MyInput(\' Hello?\' ), expected_output=MyOutput(\' Hi!\' )),
64+ ],
65+ )
6666
67- # Add cases using pydantic-evals Case objects
68- client.add_cases(
69- dataset[\' id\' ],
70- cases=[
71- Case(name=\' q1\' , inputs=MyInput(\' Hello?\' ), expected_output=MyOutput(\' Hi!\' )),
72- ],
73- )
67+
68+ with LogfireAPIClient(api_key=\' your-api-key\' ) as client:
69+ # Publish the local dataset to hosted
70+ client.push_dataset(local_dataset)
7471
7572 # Get as pydantic-evals Dataset
7673 dataset = client.get_dataset(\' qa-dataset\' , MyInput, MyOutput)
@@ -93,7 +90,7 @@ class LogfireAPIClient(_BaseLogfireAPIClient[Client]):
9390 Returns:
9491 List of dataset summaries with id, name, description, case_count, etc.
9592 """
96- def create_dataset (self , name : str , * , input_type : type [Any ] | None = None , output_type : type [Any ] | None = None , metadata_type : type [Any ] | None = None , description : str | None = None , guidance : str | None = None , ai_managed_guidance : bool = False ) -> dict [str , Any ]:
93+ def create_dataset (self , name : str , * , input_type : type [Any ] | None = None , output_type : type [Any ] | None = None , metadata_type : type [Any ] | None = None , description : str | None = None ) -> dict [str , Any ]:
9794 '''Create a new dataset with optional type schemas.
9895
9996 Args:
@@ -102,8 +99,6 @@ class LogfireAPIClient(_BaseLogfireAPIClient[Client]):
10299 output_type: Type for expected outputs. JSON schema will be generated from this type.
103100 metadata_type: Type for case metadata. JSON schema will be generated from this type.
104101 description: Optional description of the dataset.
105- guidance: Instructions for AI-assisted population.
106- ai_managed_guidance: Whether guidance is managed by AI.
107102
108103 Returns:
109104 The created dataset.
@@ -130,7 +125,7 @@ class LogfireAPIClient(_BaseLogfireAPIClient[Client]):
130125 )
131126 ```
132127 '''
133- def update_dataset (self , id_or_name : str , * , name : str = ..., input_type : type [Any ] | None = None , output_type : type [Any ] | None = None , metadata_type : type [Any ] | None = None , description : str | None = ..., guidance : str | None = ..., ai_managed_guidance : bool | None = None ) -> dict [str , Any ]:
128+ def update_dataset (self , id_or_name : str , * , name : str = ..., input_type : type [Any ] | None = None , output_type : type [Any ] | None = None , metadata_type : type [Any ] | None = None , description : str | None = ...) -> dict [str , Any ]:
134129 """Update an existing dataset.
135130
136131 Args:
@@ -140,8 +135,6 @@ class LogfireAPIClient(_BaseLogfireAPIClient[Client]):
140135 output_type: New output type (generates schema).
141136 metadata_type: New metadata type (generates schema).
142137 description: New description. Pass None to clear.
143- guidance: New guidance instructions. Pass None to clear.
144- ai_managed_guidance: Whether guidance is managed by AI.
145138
146139 Returns:
147140 The updated dataset.
@@ -158,12 +151,11 @@ class LogfireAPIClient(_BaseLogfireAPIClient[Client]):
158151 Raises:
159152 DatasetNotFoundError: If the dataset does not exist.
160153 """
161- def list_cases (self , dataset_id_or_name : str , * , tags : list [ str ] | None = None ) -> list [dict [str , Any ]]:
154+ def list_cases (self , dataset_id_or_name : str ) -> list [dict [str , Any ]]:
162155 """List all cases in a dataset.
163156
164157 Args:
165158 dataset_id_or_name: The dataset ID (UUID) or name.
166- tags: Optional list of tags to filter cases by.
167159
168160 Returns:
169161 List of cases with full details.
@@ -185,7 +177,79 @@ class LogfireAPIClient(_BaseLogfireAPIClient[Client]):
185177 DatasetNotFoundError: If the dataset does not exist.
186178 CaseNotFoundError: If the case does not exist.
187179 """
188- def add_cases (self , dataset_id_or_name : str , cases : Sequence [Case [InputsT , OutputT , MetadataT ]] | Sequence [dict [str , Any ]], * , tags : list [str ] | None = None , on_conflict : str = 'update' ) -> list [dict [str , Any ]]:
180+ def push_dataset (self , dataset : Dataset [InputsT , OutputT , MetadataT ], * , name : str | None = None , description : str | None = ..., on_case_conflict : Literal ['update' , 'error' ] = 'update' ) -> dict [str , Any ]:
181+ '''Publish a local `pydantic_evals.Dataset` to the hosted datasets API.
182+
183+ This is the high-level "push my dataset to hosted" helper. It creates a
184+ hosted dataset when one does not exist yet, updates the hosted dataset
185+ when one already exists with the same name, uploads all local cases
186+ through the existing import/upsert API, and finally returns hosted
187+ dataset metadata.
188+
189+ The JSON schemas for inputs, expected outputs, and metadata are
190+ inferred from the `Dataset[InputsT, OutputT, MetadataT]` generic
191+ parameters of the dataset you pass in — instantiate your local dataset
192+ with the types you want hosted.
193+
194+ Args:
195+ dataset: The local `pydantic_evals.Dataset` to publish. Case-level
196+ evaluators are uploaded with their cases. Dataset-level
197+ `evaluators` and `report_evaluators` are not supported yet and
198+ will raise `ValueError`.
199+ name: Optional hosted dataset name override. Defaults to
200+ `dataset.name`.
201+ description: Hosted dataset description. Omit this argument to leave
202+ the existing description unchanged when updating an existing
203+ dataset. Pass `None` to clear the description on update. On
204+ initial create, `None` means no description is set.
205+ on_case_conflict: Conflict behavior for uploaded cases. The default
206+ `\' update\' ` makes repeated pushes idempotent for named cases.
207+ Pass `\' error\' ` to fail instead of updating an existing case with
208+ the same name.
209+
210+ Returns:
211+ Hosted dataset metadata as returned by
212+ `get_dataset(..., include_cases=False)`.
213+
214+ Raises:
215+ ValueError: If neither `dataset.name` nor `name` is provided, or if
216+ the dataset contains unsupported dataset-level evaluators.
217+ DatasetApiError: If the API returns an error other than the expected
218+ `409` conflict used to trigger an update flow.
219+ DatasetNotFoundError: If the hosted dataset cannot be fetched after
220+ the push completes.
221+
222+ Example:
223+ ```python skip-run="true" skip-reason="external-connection"
224+ from dataclasses import dataclass
225+
226+ from pydantic_evals import Case, Dataset
227+
228+
229+ @dataclass
230+ class MyInput:
231+ question: str
232+
233+
234+ @dataclass
235+ class MyOutput:
236+ answer: str
237+
238+
239+ local_dataset = Dataset[MyInput, MyOutput, None](
240+ name=\' qa-dataset\' ,
241+ cases=[
242+ Case(name=\' q1\' , inputs=MyInput(\' Hello?\' ), expected_output=MyOutput(\' Hi!\' )),
243+ ],
244+ )
245+
246+ dataset_info = client.push_dataset(
247+ local_dataset,
248+ description=\' Golden test cases for the Q&A task\' ,
249+ )
250+ ```
251+ '''
252+ def add_cases (self , dataset_id_or_name : str , cases : Sequence [Case [InputsT , OutputT , MetadataT ]] | Sequence [dict [str , Any ]], * , on_conflict : str = 'update' ) -> list [dict [str , Any ]]:
189253 '''Add cases to a dataset.
190254
191255 Accepts either pydantic-evals Case objects or plain dicts.
@@ -197,7 +261,6 @@ class LogfireAPIClient(_BaseLogfireAPIClient[Client]):
197261 Args:
198262 dataset_id_or_name: The dataset ID (UUID) or name.
199263 cases: A sequence of pydantic-evals Case objects or dicts.
200- tags: Optional list of tags to associate with all cases.
201264 on_conflict: Conflict resolution strategy: `\' update\' ` (default) to
202265 upsert cases with matching names, or `\' error\' ` to fail on conflicts.
203266
@@ -220,7 +283,7 @@ class LogfireAPIClient(_BaseLogfireAPIClient[Client]):
220283 )
221284 ```
222285 '''
223- def update_case (self , dataset_id_or_name : str , case_id : str , * , name : str | None = ..., inputs : Any | None = None , expected_output : Any | None = ..., metadata : Any | None = ..., evaluators : Sequence [Evaluator [Any , Any , Any ]] | None = ..., tags : list [ str ] | None = ... ) -> dict [str , Any ]:
286+ def update_case (self , dataset_id_or_name : str , case_id : str , * , name : str | None = ..., inputs : Any | None = None , expected_output : Any | None = ..., metadata : Any | None = ..., evaluators : Sequence [Evaluator [Any , Any , Any ]] | None = ...) -> dict [str , Any ]:
224287 """Update an existing case.
225288
226289 Args:
@@ -231,7 +294,6 @@ class LogfireAPIClient(_BaseLogfireAPIClient[Client]):
231294 expected_output: New expected output (dict or typed object). Pass None to clear.
232295 metadata: New metadata (dict or typed object). Pass None to clear.
233296 evaluators: New evaluators. Pass None to clear.
234- tags: New tags for the case. Pass None to clear.
235297
236298 Returns:
237299 The updated case.
@@ -266,17 +328,48 @@ class AsyncLogfireAPIClient(_BaseLogfireAPIClient[AsyncClient]):
266328 async def __aexit__ (self , exc_type : type [BaseException ] | None = None , exc_value : BaseException | None = None , traceback : TracebackType | None = None ) -> None : ...
267329 async def list_datasets (self ) -> list [dict [str , Any ]]:
268330 """List all datasets."""
269- async def create_dataset (self , name : str , * , input_type : type [Any ] | None = None , output_type : type [Any ] | None = None , metadata_type : type [Any ] | None = None , description : str | None = None , guidance : str | None = None , ai_managed_guidance : bool = False ) -> dict [str , Any ]:
331+ async def create_dataset (self , name : str , * , input_type : type [Any ] | None = None , output_type : type [Any ] | None = None , metadata_type : type [Any ] | None = None , description : str | None = None ) -> dict [str , Any ]:
270332 """Create a new dataset."""
271- async def update_dataset (self , id_or_name : str , * , name : str = ..., input_type : type [Any ] | None = None , output_type : type [Any ] | None = None , metadata_type : type [Any ] | None = None , description : str | None = ..., guidance : str | None = ..., ai_managed_guidance : bool | None = None ) -> dict [str , Any ]:
333+ async def update_dataset (self , id_or_name : str , * , name : str = ..., input_type : type [Any ] | None = None , output_type : type [Any ] | None = None , metadata_type : type [Any ] | None = None , description : str | None = ...) -> dict [str , Any ]:
272334 """Update an existing dataset."""
273335 async def delete_dataset (self , id_or_name : str ) -> None :
274336 """Delete a dataset."""
275- async def list_cases (self , dataset_id_or_name : str , * , tags : list [ str ] | None = None ) -> list [dict [str , Any ]]:
337+ async def list_cases (self , dataset_id_or_name : str ) -> list [dict [str , Any ]]:
276338 """List all cases in a dataset."""
277339 async def get_case (self , dataset_id_or_name : str , case_id : str ) -> dict [str , Any ]:
278340 """Get a specific case from a dataset."""
279- async def add_cases (self , dataset_id_or_name : str , cases : Sequence [Case [InputsT , OutputT , MetadataT ]] | Sequence [dict [str , Any ]], * , tags : list [str ] | None = None , on_conflict : str = 'update' ) -> list [dict [str , Any ]]:
341+ async def push_dataset (self , dataset : Dataset [InputsT , OutputT , MetadataT ], * , name : str | None = None , description : str | None = ..., on_case_conflict : Literal ['update' , 'error' ] = 'update' ) -> dict [str , Any ]:
342+ """Async version of `LogfireAPIClient.push_dataset`.
343+
344+ Args:
345+ dataset: The local `pydantic_evals.Dataset` to publish. Case-level
346+ evaluators are uploaded with their cases. Dataset-level
347+ `evaluators` and `report_evaluators` are not supported yet and
348+ will raise `ValueError`.
349+ name: Optional hosted dataset name override. Defaults to
350+ `dataset.name`.
351+ description: Hosted dataset description. Omit this argument to leave
352+ the existing description unchanged when updating an existing
353+ dataset. Pass `None` to clear the description on update. On
354+ initial create, `None` means no description is set.
355+ on_case_conflict: Conflict behavior for uploaded cases. The default
356+ `'update'` makes repeated pushes idempotent for named cases.
357+ Pass `'error'` to fail instead of updating an existing case with
358+ the same name.
359+
360+ Returns:
361+ Hosted dataset metadata as returned by
362+ `get_dataset(..., include_cases=False)`.
363+
364+ Raises:
365+ ValueError: If neither `dataset.name` nor `name` is provided, or if
366+ the dataset contains unsupported dataset-level evaluators.
367+ DatasetApiError: If the API returns an error other than the expected
368+ `409` conflict used to trigger an update flow.
369+ DatasetNotFoundError: If the hosted dataset cannot be fetched after
370+ the push completes.
371+ """
372+ async def add_cases (self , dataset_id_or_name : str , cases : Sequence [Case [InputsT , OutputT , MetadataT ]] | Sequence [dict [str , Any ]], * , on_conflict : str = 'update' ) -> list [dict [str , Any ]]:
280373 """Add cases to a dataset.
281374
282375 Accepts either pydantic-evals Case objects or plain dicts.
@@ -285,7 +378,7 @@ class AsyncLogfireAPIClient(_BaseLogfireAPIClient[AsyncClient]):
285378 case in the dataset are updated; cases without a name or with a new name
286379 are created. Set `on_conflict='error'` to fail on name conflicts instead.
287380 """
288- async def update_case (self , dataset_id_or_name : str , case_id : str , * , name : str | None = ..., inputs : Any | None = None , expected_output : Any | None = ..., metadata : Any | None = ..., evaluators : Sequence [Evaluator [Any , Any , Any ]] | None = ..., tags : list [ str ] | None = ... ) -> dict [str , Any ]:
381+ async def update_case (self , dataset_id_or_name : str , case_id : str , * , name : str | None = ..., inputs : Any | None = None , expected_output : Any | None = ..., metadata : Any | None = ..., evaluators : Sequence [Evaluator [Any , Any , Any ]] | None = ...) -> dict [str , Any ]:
289382 """Update an existing case."""
290383 async def delete_case (self , dataset_id_or_name : str , case_id : str ) -> None :
291384 """Delete a case from a dataset."""
0 commit comments