arize-phoenix 12.3.0__py3-none-any.whl → 12.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of arize-phoenix might be problematic. Click here for more details.

Files changed (37) hide show
  1. {arize_phoenix-12.3.0.dist-info → arize_phoenix-12.4.0.dist-info}/METADATA +2 -1
  2. {arize_phoenix-12.3.0.dist-info → arize_phoenix-12.4.0.dist-info}/RECORD +37 -37
  3. phoenix/auth.py +19 -0
  4. phoenix/config.py +302 -53
  5. phoenix/db/README.md +546 -28
  6. phoenix/server/api/routers/auth.py +21 -30
  7. phoenix/server/api/routers/oauth2.py +213 -24
  8. phoenix/server/api/routers/v1/__init__.py +2 -3
  9. phoenix/server/api/routers/v1/annotation_configs.py +12 -29
  10. phoenix/server/api/routers/v1/annotations.py +21 -22
  11. phoenix/server/api/routers/v1/datasets.py +38 -56
  12. phoenix/server/api/routers/v1/documents.py +2 -3
  13. phoenix/server/api/routers/v1/evaluations.py +12 -24
  14. phoenix/server/api/routers/v1/experiment_evaluations.py +2 -3
  15. phoenix/server/api/routers/v1/experiment_runs.py +9 -10
  16. phoenix/server/api/routers/v1/experiments.py +16 -17
  17. phoenix/server/api/routers/v1/projects.py +15 -21
  18. phoenix/server/api/routers/v1/prompts.py +30 -31
  19. phoenix/server/api/routers/v1/sessions.py +2 -5
  20. phoenix/server/api/routers/v1/spans.py +35 -26
  21. phoenix/server/api/routers/v1/traces.py +11 -19
  22. phoenix/server/api/routers/v1/users.py +14 -23
  23. phoenix/server/api/routers/v1/utils.py +3 -7
  24. phoenix/server/app.py +1 -2
  25. phoenix/server/authorization.py +2 -3
  26. phoenix/server/bearer_auth.py +4 -5
  27. phoenix/server/oauth2.py +172 -5
  28. phoenix/server/static/.vite/manifest.json +9 -9
  29. phoenix/server/static/assets/{components-Bs8eJEpU.js → components-BvsExS75.js} +110 -120
  30. phoenix/server/static/assets/{index-C6WEu5UP.js → index-iq8WDxat.js} +1 -1
  31. phoenix/server/static/assets/{pages-D-n2pkoG.js → pages-Ckg4SLQ9.js} +4 -4
  32. phoenix/trace/attributes.py +80 -13
  33. phoenix/version.py +1 -1
  34. {arize_phoenix-12.3.0.dist-info → arize_phoenix-12.4.0.dist-info}/WHEEL +0 -0
  35. {arize_phoenix-12.3.0.dist-info → arize_phoenix-12.4.0.dist-info}/entry_points.txt +0 -0
  36. {arize_phoenix-12.3.0.dist-info → arize_phoenix-12.4.0.dist-info}/licenses/IP_NOTICE +0 -0
  37. {arize_phoenix-12.3.0.dist-info → arize_phoenix-12.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,11 +7,6 @@ from sqlalchemy import delete, select
7
7
  from sqlalchemy.exc import IntegrityError as PostgreSQLIntegrityError
8
8
  from sqlean.dbapi2 import IntegrityError as SQLiteIntegrityError # type: ignore[import-untyped]
9
9
  from starlette.requests import Request
10
- from starlette.status import (
11
- HTTP_400_BAD_REQUEST,
12
- HTTP_404_NOT_FOUND,
13
- HTTP_409_CONFLICT,
14
- )
15
10
  from strawberry.relay import GlobalID
16
11
  from typing_extensions import TypeAlias, assert_never
17
12
 
@@ -206,7 +201,7 @@ async def list_annotation_configs(
206
201
  except ValueError:
207
202
  raise HTTPException(
208
203
  detail=f"Invalid cursor: {cursor}",
209
- status_code=HTTP_400_BAD_REQUEST,
204
+ status_code=400,
210
205
  )
211
206
  if cursor_gid.type_name not in (
212
207
  CategoricalAnnotationConfigType.__name__,
@@ -215,7 +210,7 @@ async def list_annotation_configs(
215
210
  ):
216
211
  raise HTTPException(
217
212
  detail=f"Invalid cursor: {cursor}",
218
- status_code=HTTP_400_BAD_REQUEST,
213
+ status_code=400,
219
214
  )
220
215
  cursor_id = int(cursor_gid.node_id)
221
216
 
@@ -261,9 +256,7 @@ async def get_annotation_config_by_name_or_id(
261
256
  query = query.where(models.AnnotationConfig.name == config_identifier)
262
257
  config = await session.scalar(query)
263
258
  if not config:
264
- raise HTTPException(
265
- status_code=HTTP_404_NOT_FOUND, detail="Annotation configuration not found"
266
- )
259
+ raise HTTPException(status_code=404, detail="Annotation configuration not found")
267
260
  return GetAnnotationConfigResponseBody(data=db_to_api_annotation_config(config))
268
261
 
269
262
 
@@ -282,7 +275,7 @@ async def create_annotation_config(
282
275
  try:
283
276
  db_config = _to_db_annotation_config(input_config)
284
277
  except ValueError as error:
285
- raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail=str(error))
278
+ raise HTTPException(status_code=400, detail=str(error))
286
279
 
287
280
  async with request.app.state.db() as session:
288
281
  annotation_config = models.AnnotationConfig(
@@ -294,7 +287,7 @@ async def create_annotation_config(
294
287
  await session.commit()
295
288
  except (PostgreSQLIntegrityError, SQLiteIntegrityError):
296
289
  raise HTTPException(
297
- status_code=HTTP_409_CONFLICT,
290
+ status_code=409,
298
291
  detail="The name of the annotation configuration is already taken",
299
292
  )
300
293
  return CreateAnnotationConfigResponseBody(
@@ -321,22 +314,18 @@ async def update_annotation_config(
321
314
  ContinuousAnnotationConfigType.__name__,
322
315
  FreeformAnnotationConfigType.__name__,
323
316
  ):
324
- raise HTTPException(
325
- status_code=HTTP_400_BAD_REQUEST, detail="Invalid annotation configuration ID"
326
- )
317
+ raise HTTPException(status_code=400, detail="Invalid annotation configuration ID")
327
318
  config_rowid = int(config_gid.node_id)
328
319
 
329
320
  try:
330
321
  db_config = _to_db_annotation_config(input_config)
331
322
  except ValueError as error:
332
- raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail=str(error))
323
+ raise HTTPException(status_code=400, detail=str(error))
333
324
 
334
325
  async with request.app.state.db() as session:
335
326
  existing_config = await session.get(models.AnnotationConfig, config_rowid)
336
327
  if not existing_config:
337
- raise HTTPException(
338
- status_code=HTTP_404_NOT_FOUND, detail="Annotation configuration not found"
339
- )
328
+ raise HTTPException(status_code=404, detail="Annotation configuration not found")
340
329
 
341
330
  existing_config.name = input_config.name
342
331
  existing_config.config = db_config
@@ -345,7 +334,7 @@ async def update_annotation_config(
345
334
  await session.commit()
346
335
  except (PostgreSQLIntegrityError, SQLiteIntegrityError):
347
336
  raise HTTPException(
348
- status_code=HTTP_409_CONFLICT,
337
+ status_code=409,
349
338
  detail="The name of the annotation configuration is already taken",
350
339
  )
351
340
 
@@ -366,9 +355,7 @@ async def delete_annotation_config(
366
355
  ContinuousAnnotationConfigType.__name__,
367
356
  FreeformAnnotationConfigType.__name__,
368
357
  ):
369
- raise HTTPException(
370
- status_code=HTTP_400_BAD_REQUEST, detail="Invalid annotation configuration ID"
371
- )
358
+ raise HTTPException(status_code=400, detail="Invalid annotation configuration ID")
372
359
  config_rowid = int(config_gid.node_id)
373
360
  async with request.app.state.db() as session:
374
361
  stmt = (
@@ -378,9 +365,7 @@ async def delete_annotation_config(
378
365
  )
379
366
  annotation_config = await session.scalar(stmt)
380
367
  if annotation_config is None:
381
- raise HTTPException(
382
- status_code=HTTP_404_NOT_FOUND, detail="Annotation configuration not found"
383
- )
368
+ raise HTTPException(status_code=404, detail="Annotation configuration not found")
384
369
  await session.commit()
385
370
  return DeleteAnnotationConfigResponseBody(data=db_to_api_annotation_config(annotation_config))
386
371
 
@@ -400,9 +385,7 @@ def _get_annotation_config_db_id(config_gid: str) -> int:
400
385
  def _reserve_note_annotation_name(data: AnnotationConfigData) -> str:
401
386
  name = data.name
402
387
  if name == "note":
403
- raise HTTPException(
404
- status_code=HTTP_409_CONFLICT, detail="The name 'note' is reserved for span notes"
405
- )
388
+ raise HTTPException(status_code=409, detail="The name 'note' is reserved for span notes")
406
389
  return name
407
390
 
408
391
 
@@ -8,7 +8,6 @@ from fastapi import APIRouter, HTTPException, Path, Query
8
8
  from pydantic import Field
9
9
  from sqlalchemy import exists, select
10
10
  from starlette.requests import Request
11
- from starlette.status import HTTP_200_OK, HTTP_404_NOT_FOUND, HTTP_422_UNPROCESSABLE_ENTITY
12
11
  from strawberry.relay import GlobalID
13
12
 
14
13
  from phoenix.db import models
@@ -198,11 +197,11 @@ class SessionAnnotationsResponseBody(PaginatedResponseBody[SessionAnnotation]):
198
197
  "/projects/{project_identifier}/span_annotations",
199
198
  operation_id="listSpanAnnotationsBySpanIds",
200
199
  summary="Get span annotations for a list of span_ids.",
201
- status_code=HTTP_200_OK,
200
+ status_code=200,
202
201
  responses=add_errors_to_responses(
203
202
  [
204
- {"status_code": HTTP_404_NOT_FOUND, "description": "Project or spans not found"},
205
- {"status_code": HTTP_422_UNPROCESSABLE_ENTITY, "description": "Invalid parameters"},
203
+ {"status_code": 404, "description": "Project or spans not found"},
204
+ {"status_code": 422, "description": "Invalid parameters"},
206
205
  ]
207
206
  ),
208
207
  )
@@ -240,7 +239,7 @@ async def list_span_annotations(
240
239
  span_ids = list({*span_ids})
241
240
  if len(span_ids) > MAX_SPAN_IDS:
242
241
  raise HTTPException(
243
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
242
+ status_code=422,
244
243
  detail=f"Too many span_ids supplied: {len(span_ids)} (max {MAX_SPAN_IDS})",
245
244
  )
246
245
 
@@ -248,7 +247,7 @@ async def list_span_annotations(
248
247
  project = await _get_project_by_identifier(session, project_identifier)
249
248
  if not project:
250
249
  raise HTTPException(
251
- status_code=HTTP_404_NOT_FOUND,
250
+ status_code=404,
252
251
  detail=f"Project with identifier {project_identifier} not found",
253
252
  )
254
253
 
@@ -280,7 +279,7 @@ async def list_span_annotations(
280
279
  cursor_id = int(GlobalID.from_id(cursor).node_id)
281
280
  except ValueError:
282
281
  raise HTTPException(
283
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
282
+ status_code=422,
284
283
  detail="Invalid cursor value",
285
284
  )
286
285
  stmt = stmt.where(models.SpanAnnotation.id <= cursor_id)
@@ -310,7 +309,7 @@ async def list_span_annotations(
310
309
  if not spans_exist:
311
310
  raise HTTPException(
312
311
  detail="None of the supplied span_ids exist in this project",
313
- status_code=HTTP_404_NOT_FOUND,
312
+ status_code=404,
314
313
  )
315
314
 
316
315
  return SpanAnnotationsResponseBody(data=[], next_cursor=None)
@@ -343,11 +342,11 @@ async def list_span_annotations(
343
342
  "/projects/{project_identifier}/trace_annotations",
344
343
  operation_id="listTraceAnnotationsByTraceIds",
345
344
  summary="Get trace annotations for a list of trace_ids.",
346
- status_code=HTTP_200_OK,
345
+ status_code=200,
347
346
  responses=add_errors_to_responses(
348
347
  [
349
- {"status_code": HTTP_404_NOT_FOUND, "description": "Project or traces not found"},
350
- {"status_code": HTTP_422_UNPROCESSABLE_ENTITY, "description": "Invalid parameters"},
348
+ {"status_code": 404, "description": "Project or traces not found"},
349
+ {"status_code": 422, "description": "Invalid parameters"},
351
350
  ]
352
351
  ),
353
352
  )
@@ -385,7 +384,7 @@ async def list_trace_annotations(
385
384
  trace_ids = list({*trace_ids})
386
385
  if len(trace_ids) > MAX_TRACE_IDS:
387
386
  raise HTTPException(
388
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
387
+ status_code=422,
389
388
  detail=f"Too many trace_ids supplied: {len(trace_ids)} (max {MAX_TRACE_IDS})",
390
389
  )
391
390
 
@@ -393,7 +392,7 @@ async def list_trace_annotations(
393
392
  project = await _get_project_by_identifier(session, project_identifier)
394
393
  if not project:
395
394
  raise HTTPException(
396
- status_code=HTTP_404_NOT_FOUND,
395
+ status_code=404,
397
396
  detail=f"Project with identifier {project_identifier} not found",
398
397
  )
399
398
 
@@ -424,7 +423,7 @@ async def list_trace_annotations(
424
423
  cursor_id = int(GlobalID.from_id(cursor).node_id)
425
424
  except ValueError:
426
425
  raise HTTPException(
427
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
426
+ status_code=422,
428
427
  detail="Invalid cursor value",
429
428
  )
430
429
  stmt = stmt.where(models.TraceAnnotation.id <= cursor_id)
@@ -450,7 +449,7 @@ async def list_trace_annotations(
450
449
  if not traces_exist:
451
450
  raise HTTPException(
452
451
  detail="None of the supplied trace_ids exist in this project",
453
- status_code=HTTP_404_NOT_FOUND,
452
+ status_code=404,
454
453
  )
455
454
 
456
455
  return TraceAnnotationsResponseBody(data=[], next_cursor=None)
@@ -483,11 +482,11 @@ async def list_trace_annotations(
483
482
  "/projects/{project_identifier}/session_annotations",
484
483
  operation_id="listSessionAnnotationsBySessionIds",
485
484
  summary="Get session annotations for a list of session_ids.",
486
- status_code=HTTP_200_OK,
485
+ status_code=200,
487
486
  responses=add_errors_to_responses(
488
487
  [
489
- {"status_code": HTTP_404_NOT_FOUND, "description": "Project or sessions not found"},
490
- {"status_code": HTTP_422_UNPROCESSABLE_ENTITY, "description": "Invalid parameters"},
488
+ {"status_code": 404, "description": "Project or sessions not found"},
489
+ {"status_code": 422, "description": "Invalid parameters"},
491
490
  ]
492
491
  ),
493
492
  )
@@ -525,7 +524,7 @@ async def list_session_annotations(
525
524
  session_ids = list({*session_ids})
526
525
  if len(session_ids) > MAX_SESSION_IDS:
527
526
  raise HTTPException(
528
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
527
+ status_code=422,
529
528
  detail=f"Too many session_ids supplied: {len(session_ids)} (max {MAX_SESSION_IDS})",
530
529
  )
531
530
 
@@ -533,7 +532,7 @@ async def list_session_annotations(
533
532
  project = await _get_project_by_identifier(session, project_identifier)
534
533
  if not project:
535
534
  raise HTTPException(
536
- status_code=HTTP_404_NOT_FOUND,
535
+ status_code=404,
537
536
  detail=f"Project with identifier {project_identifier} not found",
538
537
  )
539
538
 
@@ -571,7 +570,7 @@ async def list_session_annotations(
571
570
  cursor_id = int(GlobalID.from_id(cursor).node_id)
572
571
  except ValueError:
573
572
  raise HTTPException(
574
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
573
+ status_code=422,
575
574
  detail="Invalid cursor value",
576
575
  )
577
576
  stmt = stmt.where(models.ProjectSessionAnnotation.id <= cursor_id)
@@ -597,7 +596,7 @@ async def list_session_annotations(
597
596
  if not sessions_exist:
598
597
  raise HTTPException(
599
598
  detail="None of the supplied session_ids exist in this project",
600
- status_code=HTTP_404_NOT_FOUND,
599
+ status_code=404,
601
600
  )
602
601
 
603
602
  return SessionAnnotationsResponseBody(data=[], next_cursor=None)
@@ -23,14 +23,6 @@ from starlette.concurrency import run_in_threadpool
23
23
  from starlette.datastructures import FormData, UploadFile
24
24
  from starlette.requests import Request
25
25
  from starlette.responses import Response
26
- from starlette.status import (
27
- HTTP_200_OK,
28
- HTTP_204_NO_CONTENT,
29
- HTTP_404_NOT_FOUND,
30
- HTTP_409_CONFLICT,
31
- HTTP_422_UNPROCESSABLE_ENTITY,
32
- HTTP_429_TOO_MANY_REQUESTS,
33
- )
34
26
  from strawberry.relay import GlobalID
35
27
  from typing_extensions import TypeAlias, assert_never
36
28
 
@@ -91,7 +83,7 @@ class ListDatasetsResponseBody(PaginatedResponseBody[Dataset]):
91
83
  "/datasets",
92
84
  operation_id="listDatasets",
93
85
  summary="List datasets",
94
- responses=add_errors_to_responses([HTTP_422_UNPROCESSABLE_ENTITY]),
86
+ responses=add_errors_to_responses([422]),
95
87
  )
96
88
  async def list_datasets(
97
89
  request: Request,
@@ -125,7 +117,7 @@ async def list_datasets(
125
117
  except ValueError:
126
118
  raise HTTPException(
127
119
  detail=f"Invalid cursor format: {cursor}",
128
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
120
+ status_code=422,
129
121
  )
130
122
  if name:
131
123
  query = query.filter(models.Dataset.name == name)
@@ -164,11 +156,11 @@ async def list_datasets(
164
156
  "/datasets/{id}",
165
157
  operation_id="deleteDatasetById",
166
158
  summary="Delete dataset by ID",
167
- status_code=HTTP_204_NO_CONTENT,
159
+ status_code=204,
168
160
  responses=add_errors_to_responses(
169
161
  [
170
- {"status_code": HTTP_404_NOT_FOUND, "description": "Dataset not found"},
171
- {"status_code": HTTP_422_UNPROCESSABLE_ENTITY, "description": "Invalid dataset ID"},
162
+ {"status_code": 404, "description": "Dataset not found"},
163
+ {"status_code": 422, "description": "Invalid dataset ID"},
172
164
  ]
173
165
  ),
174
166
  )
@@ -182,11 +174,9 @@ async def delete_dataset(
182
174
  DATASET_NODE_NAME,
183
175
  )
184
176
  except ValueError:
185
- raise HTTPException(
186
- detail=f"Invalid Dataset ID: {id}", status_code=HTTP_422_UNPROCESSABLE_ENTITY
187
- )
177
+ raise HTTPException(detail=f"Invalid Dataset ID: {id}", status_code=422)
188
178
  else:
189
- raise HTTPException(detail="Missing Dataset ID", status_code=HTTP_422_UNPROCESSABLE_ENTITY)
179
+ raise HTTPException(detail="Missing Dataset ID", status_code=422)
190
180
  project_names_stmt = get_project_names_for_datasets(dataset_id)
191
181
  eval_trace_ids_stmt = get_eval_trace_ids_for_datasets(dataset_id)
192
182
  stmt = (
@@ -196,7 +186,7 @@ async def delete_dataset(
196
186
  project_names = await session.scalars(project_names_stmt)
197
187
  eval_trace_ids = await session.scalars(eval_trace_ids_stmt)
198
188
  if (await session.scalar(stmt)) is None:
199
- raise HTTPException(detail="Dataset does not exist", status_code=HTTP_404_NOT_FOUND)
189
+ raise HTTPException(detail="Dataset does not exist", status_code=404)
200
190
  tasks = BackgroundTasks()
201
191
  tasks.add_task(delete_projects, request.app.state.db, *project_names)
202
192
  tasks.add_task(delete_traces, request.app.state.db, *eval_trace_ids)
@@ -214,7 +204,7 @@ class GetDatasetResponseBody(ResponseBody[DatasetWithExampleCount]):
214
204
  "/datasets/{id}",
215
205
  operation_id="getDataset",
216
206
  summary="Get dataset by ID",
217
- responses=add_errors_to_responses([HTTP_404_NOT_FOUND]),
207
+ responses=add_errors_to_responses([404]),
218
208
  )
219
209
  async def get_dataset(
220
210
  request: Request, id: str = Path(description="The ID of the dataset")
@@ -222,9 +212,7 @@ async def get_dataset(
222
212
  dataset_id = GlobalID.from_id(id)
223
213
 
224
214
  if (type_name := dataset_id.type_name) != DATASET_NODE_NAME:
225
- raise HTTPException(
226
- detail=f"ID {dataset_id} refers to a f{type_name}", status_code=HTTP_404_NOT_FOUND
227
- )
215
+ raise HTTPException(detail=f"ID {dataset_id} refers to a f{type_name}", status_code=404)
228
216
  async with request.app.state.db() as session:
229
217
  result = await session.execute(
230
218
  select(models.Dataset, models.Dataset.example_count).filter(
@@ -235,9 +223,7 @@ async def get_dataset(
235
223
  dataset = dataset_query[0] if dataset_query else None
236
224
  example_count = dataset_query[1] if dataset_query else 0
237
225
  if dataset is None:
238
- raise HTTPException(
239
- detail=f"Dataset with ID {dataset_id} not found", status_code=HTTP_404_NOT_FOUND
240
- )
226
+ raise HTTPException(detail=f"Dataset with ID {dataset_id} not found", status_code=404)
241
227
 
242
228
  dataset = DatasetWithExampleCount(
243
229
  id=str(dataset_id),
@@ -266,7 +252,7 @@ class ListDatasetVersionsResponseBody(PaginatedResponseBody[DatasetVersion]):
266
252
  "/datasets/{id}/versions",
267
253
  operation_id="listDatasetVersionsByDatasetId",
268
254
  summary="List dataset versions",
269
- responses=add_errors_to_responses([HTTP_422_UNPROCESSABLE_ENTITY]),
255
+ responses=add_errors_to_responses([422]),
270
256
  )
271
257
  async def list_dataset_versions(
272
258
  request: Request,
@@ -288,12 +274,12 @@ async def list_dataset_versions(
288
274
  except ValueError:
289
275
  raise HTTPException(
290
276
  detail=f"Invalid Dataset ID: {id}",
291
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
277
+ status_code=422,
292
278
  )
293
279
  else:
294
280
  raise HTTPException(
295
281
  detail="Missing Dataset ID",
296
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
282
+ status_code=422,
297
283
  )
298
284
  stmt = (
299
285
  select(models.DatasetVersion)
@@ -309,7 +295,7 @@ async def list_dataset_versions(
309
295
  except ValueError:
310
296
  raise HTTPException(
311
297
  detail=f"Invalid cursor: {cursor}",
312
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
298
+ status_code=422,
313
299
  )
314
300
  max_dataset_version_id = (
315
301
  select(models.DatasetVersion.id)
@@ -348,10 +334,10 @@ class UploadDatasetResponseBody(ResponseBody[UploadDatasetData]):
348
334
  responses=add_errors_to_responses(
349
335
  [
350
336
  {
351
- "status_code": HTTP_409_CONFLICT,
337
+ "status_code": 409,
352
338
  "description": "Dataset of the same name already exists",
353
339
  },
354
- {"status_code": HTTP_422_UNPROCESSABLE_ENTITY, "description": "Invalid request body"},
340
+ {"status_code": 422, "description": "Invalid request body"},
355
341
  ]
356
342
  ),
357
343
  # FastAPI cannot generate the request body portion of the OpenAPI schema for
@@ -424,14 +410,14 @@ async def upload_dataset(
424
410
  except ValueError as e:
425
411
  raise HTTPException(
426
412
  detail=str(e),
427
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
413
+ status_code=422,
428
414
  )
429
415
  if action is DatasetAction.CREATE:
430
416
  async with request.app.state.db() as session:
431
417
  if await _check_table_exists(session, name):
432
418
  raise HTTPException(
433
419
  detail=f"Dataset with the same name already exists: {name=}",
434
- status_code=HTTP_409_CONFLICT,
420
+ status_code=409,
435
421
  )
436
422
  elif request_content_type.startswith("multipart/form-data"):
437
423
  async with request.form() as form:
@@ -448,14 +434,14 @@ async def upload_dataset(
448
434
  except ValueError as e:
449
435
  raise HTTPException(
450
436
  detail=str(e),
451
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
437
+ status_code=422,
452
438
  )
453
439
  if action is DatasetAction.CREATE:
454
440
  async with request.app.state.db() as session:
455
441
  if await _check_table_exists(session, name):
456
442
  raise HTTPException(
457
443
  detail=f"Dataset with the same name already exists: {name=}",
458
- status_code=HTTP_409_CONFLICT,
444
+ status_code=409,
459
445
  )
460
446
  content = await file.read()
461
447
  try:
@@ -472,12 +458,12 @@ async def upload_dataset(
472
458
  except ValueError as e:
473
459
  raise HTTPException(
474
460
  detail=str(e),
475
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
461
+ status_code=422,
476
462
  )
477
463
  else:
478
464
  raise HTTPException(
479
465
  detail="Invalid request Content-Type",
480
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
466
+ status_code=422,
481
467
  )
482
468
  user_id: Optional[int] = None
483
469
  if request.app.state.authentication_enabled and isinstance(request.user, PhoenixUser):
@@ -510,7 +496,7 @@ async def upload_dataset(
510
496
  except QueueFull:
511
497
  if isinstance(examples, Coroutine):
512
498
  examples.close()
513
- raise HTTPException(detail="Too many requests.", status_code=HTTP_429_TOO_MANY_REQUESTS)
499
+ raise HTTPException(detail="Too many requests.", status_code=429)
514
500
  return None
515
501
 
516
502
 
@@ -711,7 +697,7 @@ class ListDatasetExamplesResponseBody(ResponseBody[ListDatasetExamplesData]):
711
697
  "/datasets/{id}/examples",
712
698
  operation_id="getDatasetExamples",
713
699
  summary="Get examples from a dataset",
714
- responses=add_errors_to_responses([HTTP_404_NOT_FOUND]),
700
+ responses=add_errors_to_responses([404]),
715
701
  )
716
702
  async def get_dataset_examples(
717
703
  request: Request,
@@ -727,14 +713,10 @@ async def get_dataset_examples(
727
713
  version_gid = GlobalID.from_id(version_id) if version_id else None
728
714
 
729
715
  if (dataset_type := dataset_gid.type_name) != "Dataset":
730
- raise HTTPException(
731
- detail=f"ID {dataset_gid} refers to a {dataset_type}", status_code=HTTP_404_NOT_FOUND
732
- )
716
+ raise HTTPException(detail=f"ID {dataset_gid} refers to a {dataset_type}", status_code=404)
733
717
 
734
718
  if version_gid and (version_type := version_gid.type_name) != "DatasetVersion":
735
- raise HTTPException(
736
- detail=f"ID {version_gid} refers to a {version_type}", status_code=HTTP_404_NOT_FOUND
737
- )
719
+ raise HTTPException(detail=f"ID {version_gid} refers to a {version_type}", status_code=404)
738
720
 
739
721
  async with request.app.state.db() as session:
740
722
  if (
@@ -744,7 +726,7 @@ async def get_dataset_examples(
744
726
  ) is None:
745
727
  raise HTTPException(
746
728
  detail=f"No dataset with id {dataset_gid} can be found.",
747
- status_code=HTTP_404_NOT_FOUND,
729
+ status_code=404,
748
730
  )
749
731
 
750
732
  # Subquery to find the maximum created_at for each dataset_example_id
@@ -766,7 +748,7 @@ async def get_dataset_examples(
766
748
  ) is None:
767
749
  raise HTTPException(
768
750
  detail=f"No dataset version with id {version_id} can be found.",
769
- status_code=HTTP_404_NOT_FOUND,
751
+ status_code=404,
770
752
  )
771
753
  # if a version_id is provided, filter the subquery to only include revisions from that
772
754
  partial_subquery = partial_subquery.filter(
@@ -782,7 +764,7 @@ async def get_dataset_examples(
782
764
  ) is None:
783
765
  raise HTTPException(
784
766
  detail="Dataset has no versions.",
785
- status_code=HTTP_404_NOT_FOUND,
767
+ status_code=404,
786
768
  )
787
769
 
788
770
  subquery = partial_subquery.subquery()
@@ -825,10 +807,10 @@ async def get_dataset_examples(
825
807
  operation_id="getDatasetCsv",
826
808
  summary="Download dataset examples as CSV file",
827
809
  response_class=StreamingResponse,
828
- status_code=HTTP_200_OK,
810
+ status_code=200,
829
811
  responses={
830
- **add_errors_to_responses([HTTP_422_UNPROCESSABLE_ENTITY]),
831
- **add_text_csv_content_to_responses(HTTP_200_OK),
812
+ **add_errors_to_responses([422]),
813
+ **add_text_csv_content_to_responses(200),
832
814
  },
833
815
  )
834
816
  async def get_dataset_csv(
@@ -848,7 +830,7 @@ async def get_dataset_csv(
848
830
  session=session, id=id, version_id=version_id
849
831
  )
850
832
  except ValueError as e:
851
- raise HTTPException(detail=str(e), status_code=HTTP_422_UNPROCESSABLE_ENTITY)
833
+ raise HTTPException(detail=str(e), status_code=422)
852
834
  content = await run_in_threadpool(_get_content_csv, examples)
853
835
  encoded_dataset_name = urllib.parse.quote(dataset_name)
854
836
  return Response(
@@ -868,7 +850,7 @@ async def get_dataset_csv(
868
850
  responses=add_errors_to_responses(
869
851
  [
870
852
  {
871
- "status_code": HTTP_422_UNPROCESSABLE_ENTITY,
853
+ "status_code": 422,
872
854
  "description": "Invalid dataset or version ID",
873
855
  }
874
856
  ]
@@ -891,7 +873,7 @@ async def get_dataset_jsonl_openai_ft(
891
873
  session=session, id=id, version_id=version_id
892
874
  )
893
875
  except ValueError as e:
894
- raise HTTPException(detail=str(e), status_code=HTTP_422_UNPROCESSABLE_ENTITY)
876
+ raise HTTPException(detail=str(e), status_code=422)
895
877
  content = await run_in_threadpool(_get_content_jsonl_openai_ft, examples)
896
878
  encoded_dataset_name = urllib.parse.quote(dataset_name)
897
879
  response.headers["content-disposition"] = (
@@ -908,7 +890,7 @@ async def get_dataset_jsonl_openai_ft(
908
890
  responses=add_errors_to_responses(
909
891
  [
910
892
  {
911
- "status_code": HTTP_422_UNPROCESSABLE_ENTITY,
893
+ "status_code": 422,
912
894
  "description": "Invalid dataset or version ID",
913
895
  }
914
896
  ]
@@ -931,7 +913,7 @@ async def get_dataset_jsonl_openai_evals(
931
913
  session=session, id=id, version_id=version_id
932
914
  )
933
915
  except ValueError as e:
934
- raise HTTPException(detail=str(e), status_code=HTTP_422_UNPROCESSABLE_ENTITY)
916
+ raise HTTPException(detail=str(e), status_code=422)
935
917
  content = await run_in_threadpool(_get_content_jsonl_openai_evals, examples)
936
918
  encoded_dataset_name = urllib.parse.quote(dataset_name)
937
919
  response.headers["content-disposition"] = (
@@ -4,7 +4,6 @@ from fastapi import APIRouter, Depends, HTTPException, Query
4
4
  from pydantic import Field
5
5
  from sqlalchemy import select
6
6
  from starlette.requests import Request
7
- from starlette.status import HTTP_404_NOT_FOUND
8
7
  from strawberry.relay import GlobalID
9
8
 
10
9
  from phoenix.db import models
@@ -42,7 +41,7 @@ class AnnotateSpanDocumentsResponseBody(ResponseBody[list[InsertedSpanDocumentAn
42
41
  responses=add_errors_to_responses(
43
42
  [
44
43
  {
45
- "status_code": HTTP_404_NOT_FOUND,
44
+ "status_code": 404,
46
45
  "description": "Span not found",
47
46
  },
48
47
  {
@@ -102,7 +101,7 @@ async def annotate_span_documents(
102
101
  if missing_span_ids:
103
102
  raise HTTPException(
104
103
  detail=f"Spans with IDs {', '.join(missing_span_ids)} do not exist.",
105
- status_code=HTTP_404_NOT_FOUND,
104
+ status_code=404,
106
105
  )
107
106
 
108
107
  # Validate that document positions are within bounds