arize-phoenix 12.2.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 (46) hide show
  1. {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.4.0.dist-info}/METADATA +2 -1
  2. {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.4.0.dist-info}/RECORD +45 -44
  3. phoenix/auth.py +19 -0
  4. phoenix/config.py +302 -53
  5. phoenix/db/README.md +546 -28
  6. phoenix/server/api/auth_messages.py +46 -0
  7. phoenix/server/api/routers/auth.py +21 -30
  8. phoenix/server/api/routers/oauth2.py +255 -46
  9. phoenix/server/api/routers/v1/__init__.py +2 -3
  10. phoenix/server/api/routers/v1/annotation_configs.py +12 -29
  11. phoenix/server/api/routers/v1/annotations.py +21 -22
  12. phoenix/server/api/routers/v1/datasets.py +38 -56
  13. phoenix/server/api/routers/v1/documents.py +2 -3
  14. phoenix/server/api/routers/v1/evaluations.py +12 -24
  15. phoenix/server/api/routers/v1/experiment_evaluations.py +2 -3
  16. phoenix/server/api/routers/v1/experiment_runs.py +9 -10
  17. phoenix/server/api/routers/v1/experiments.py +16 -17
  18. phoenix/server/api/routers/v1/projects.py +15 -21
  19. phoenix/server/api/routers/v1/prompts.py +30 -31
  20. phoenix/server/api/routers/v1/sessions.py +2 -5
  21. phoenix/server/api/routers/v1/spans.py +35 -26
  22. phoenix/server/api/routers/v1/traces.py +11 -19
  23. phoenix/server/api/routers/v1/users.py +14 -23
  24. phoenix/server/api/routers/v1/utils.py +3 -7
  25. phoenix/server/app.py +6 -2
  26. phoenix/server/authorization.py +2 -3
  27. phoenix/server/bearer_auth.py +4 -5
  28. phoenix/server/cost_tracking/model_cost_manifest.json +54 -54
  29. phoenix/server/oauth2.py +174 -9
  30. phoenix/server/static/.vite/manifest.json +39 -39
  31. phoenix/server/static/assets/{components-BG6v0EM8.js → components-BvsExS75.js} +422 -387
  32. phoenix/server/static/assets/{index-CSVcULw1.js → index-iq8WDxat.js} +12 -12
  33. phoenix/server/static/assets/{pages-DgaM7kpM.js → pages-Ckg4SLQ9.js} +542 -488
  34. phoenix/server/static/assets/vendor-D2eEI-6h.js +914 -0
  35. phoenix/server/static/assets/{vendor-arizeai-DlOj0PQQ.js → vendor-arizeai-kfOei7nf.js} +2 -2
  36. phoenix/server/static/assets/{vendor-codemirror-B2PHH5yZ.js → vendor-codemirror-1bq_t1Ec.js} +3 -3
  37. phoenix/server/static/assets/{vendor-recharts-CKsi4IjN.js → vendor-recharts-DQ4xfrf4.js} +1 -1
  38. phoenix/server/static/assets/{vendor-shiki-DN26BkKE.js → vendor-shiki-GGmcIQxA.js} +1 -1
  39. phoenix/server/templates/index.html +1 -0
  40. phoenix/trace/attributes.py +80 -13
  41. phoenix/version.py +1 -1
  42. phoenix/server/static/assets/vendor-BqTEkGQU.js +0 -903
  43. {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.4.0.dist-info}/WHEEL +0 -0
  44. {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.4.0.dist-info}/entry_points.txt +0 -0
  45. {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.4.0.dist-info}/licenses/IP_NOTICE +0 -0
  46. {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -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
@@ -15,12 +15,6 @@ from starlette.background import BackgroundTask
15
15
  from starlette.datastructures import State
16
16
  from starlette.requests import Request
17
17
  from starlette.responses import Response, StreamingResponse
18
- from starlette.status import (
19
- HTTP_204_NO_CONTENT,
20
- HTTP_404_NOT_FOUND,
21
- HTTP_415_UNSUPPORTED_MEDIA_TYPE,
22
- HTTP_422_UNPROCESSABLE_ENTITY,
23
- )
24
18
  from typing_extensions import TypeAlias
25
19
 
26
20
  import phoenix.trace.v1 as pb
@@ -50,16 +44,16 @@ router = APIRouter(tags=["traces"], include_in_schema=True)
50
44
  dependencies=[Depends(is_not_locked)],
51
45
  operation_id="addEvaluations",
52
46
  summary="Add span, trace, or document evaluations",
53
- status_code=HTTP_204_NO_CONTENT,
47
+ status_code=204,
54
48
  responses=add_errors_to_responses(
55
49
  [
56
50
  {
57
- "status_code": HTTP_415_UNSUPPORTED_MEDIA_TYPE,
51
+ "status_code": 415,
58
52
  "description": (
59
53
  "Unsupported content type, only gzipped protobuf and pandas-arrow are supported"
60
54
  ),
61
55
  },
62
- HTTP_422_UNPROCESSABLE_ENTITY,
56
+ 422,
63
57
  ]
64
58
  ),
65
59
  openapi_extra={
@@ -80,27 +74,21 @@ async def post_evaluations(
80
74
  if content_type == "application/x-pandas-arrow":
81
75
  return await _process_pyarrow(request)
82
76
  if content_type != "application/x-protobuf":
83
- raise HTTPException(
84
- detail="Unsupported content type", status_code=HTTP_415_UNSUPPORTED_MEDIA_TYPE
85
- )
77
+ raise HTTPException(detail="Unsupported content type", status_code=415)
86
78
  body = await request.body()
87
79
  if content_encoding == "gzip":
88
80
  body = gzip.decompress(body)
89
81
  elif content_encoding:
90
- raise HTTPException(
91
- detail="Unsupported content encoding", status_code=HTTP_415_UNSUPPORTED_MEDIA_TYPE
92
- )
82
+ raise HTTPException(detail="Unsupported content encoding", status_code=415)
93
83
  evaluation = pb.Evaluation()
94
84
  try:
95
85
  evaluation.ParseFromString(body)
96
86
  except DecodeError:
97
- raise HTTPException(
98
- detail="Request body is invalid", status_code=HTTP_422_UNPROCESSABLE_ENTITY
99
- )
87
+ raise HTTPException(detail="Request body is invalid", status_code=422)
100
88
  if not evaluation.name.strip():
101
89
  raise HTTPException(
102
90
  detail="Evaluation name must not be blank/empty",
103
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
91
+ status_code=422,
104
92
  )
105
93
  await request.state.enqueue_evaluation(evaluation)
106
94
  return Response()
@@ -110,7 +98,7 @@ async def post_evaluations(
110
98
  "/evaluations",
111
99
  operation_id="getEvaluations",
112
100
  summary="Get span, trace, or document evaluations from a project",
113
- responses=add_errors_to_responses([HTTP_404_NOT_FOUND]),
101
+ responses=add_errors_to_responses([404]),
114
102
  )
115
103
  async def get_evaluations(
116
104
  request: Request,
@@ -149,7 +137,7 @@ async def get_evaluations(
149
137
  and span_evals_dataframe.empty
150
138
  and document_evals_dataframe.empty
151
139
  ):
152
- return Response(status_code=HTTP_404_NOT_FOUND)
140
+ return Response(status_code=404)
153
141
 
154
142
  evals = chain(
155
143
  map(
@@ -179,7 +167,7 @@ async def _process_pyarrow(request: Request) -> Response:
179
167
  except pa.ArrowInvalid:
180
168
  raise HTTPException(
181
169
  detail="Request body is not valid pyarrow",
182
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
170
+ status_code=422,
183
171
  )
184
172
  try:
185
173
  evaluations = Evaluations.from_pyarrow_reader(reader)
@@ -187,11 +175,11 @@ async def _process_pyarrow(request: Request) -> Response:
187
175
  if isinstance(e, PhoenixEvaluationNameIsMissing):
188
176
  raise HTTPException(
189
177
  detail="Evaluation name must not be blank/empty",
190
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
178
+ status_code=422,
191
179
  )
192
180
  raise HTTPException(
193
181
  detail="Invalid data in request body",
194
- status_code=HTTP_422_UNPROCESSABLE_ENTITY,
182
+ status_code=422,
195
183
  )
196
184
  return Response(background=BackgroundTask(_add_evaluations, request.state, evaluations))
197
185
 
@@ -5,7 +5,6 @@ from dateutil.parser import isoparse
5
5
  from fastapi import APIRouter, HTTPException
6
6
  from pydantic import Field, model_validator
7
7
  from starlette.requests import Request
8
- from starlette.status import HTTP_404_NOT_FOUND
9
8
  from strawberry.relay import GlobalID
10
9
  from typing_extensions import Self
11
10
 
@@ -72,7 +71,7 @@ class UpsertExperimentEvaluationResponseBody(
72
71
  operation_id="upsertExperimentEvaluation",
73
72
  summary="Create or update evaluation for an experiment run",
74
73
  responses=add_errors_to_responses(
75
- [{"status_code": HTTP_404_NOT_FOUND, "description": "Experiment run not found"}]
74
+ [{"status_code": 404, "description": "Experiment run not found"}]
76
75
  ),
77
76
  )
78
77
  async def upsert_experiment_evaluation(
@@ -85,7 +84,7 @@ async def upsert_experiment_evaluation(
85
84
  except ValueError:
86
85
  raise HTTPException(
87
86
  detail=f"ExperimentRun with ID {experiment_run_gid} does not exist",
88
- status_code=HTTP_404_NOT_FOUND,
87
+ status_code=404,
89
88
  )
90
89
  name = request_body.name
91
90
  annotator_kind = request_body.annotator_kind