arize-phoenix 4.26.0__py3-none-any.whl → 4.28.1__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: arize-phoenix
3
- Version: 4.26.0
3
+ Version: 4.28.1
4
4
  Summary: AI Observability and Evaluation
5
5
  Project-URL: Documentation, https://docs.arize.com/phoenix/
6
6
  Project-URL: Issues, https://github.com/Arize-ai/phoenix/issues
@@ -45,8 +45,7 @@ Requires-Dist: pydantic!=2.0.*,<3,>=1.0
45
45
  Requires-Dist: pyjwt
46
46
  Requires-Dist: python-multipart
47
47
  Requires-Dist: scikit-learn
48
- Requires-Dist: scipy!=1.14.1; platform_system == 'Darwin'
49
- Requires-Dist: scipy; platform_system != 'Darwin'
48
+ Requires-Dist: scipy
50
49
  Requires-Dist: sqlalchemy[asyncio]<3,>=2.0.4
51
50
  Requires-Dist: sqlean-py>=3.45.1
52
51
  Requires-Dist: starlette
@@ -1,12 +1,12 @@
1
1
  phoenix/__init__.py,sha256=TGNWqm2UW-l67yIRpOtmqGHVAmdoobSNqUsiTtip7uQ,1542
2
- phoenix/auth.py,sha256=N8vTFmc5BEsdX4xr6Bmh6OwBrNUQykr74LuCIkC28jA,1455
2
+ phoenix/auth.py,sha256=ugvGZlseYX9NkpWaSqb8D2kzUBlAPqT45Dx5_VUANqk,1621
3
3
  phoenix/config.py,sha256=wYA_8GSSz5rnpfIWDjeBL9ehKuTy9jqXaMZnxUqRYEU,10131
4
4
  phoenix/datetime_utils.py,sha256=yDKjwX2Vtqw9h5F_ProtP-TsXidM43uIvmJ_pOzYc9A,3405
5
5
  phoenix/exceptions.py,sha256=n2L2KKuecrdflB9MsCdAYCiSEvGJptIsfRkXMoJle7A,169
6
6
  phoenix/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
7
7
  phoenix/services.py,sha256=OyML4t2XGnlqF0JXA9_uccL8HslTABxep9Ci7MViKEU,5216
8
8
  phoenix/settings.py,sha256=cO-qgis_S27nHirTobYI9hHPfZH18R--WMmxNdsVUwc,273
9
- phoenix/version.py,sha256=4h0uTKk4f4HhC84z3Ggthi61qR_IkvFgq-cnwxm5tCU,23
9
+ phoenix/version.py,sha256=Jx45Ra5pNxAdns6eybNAM7MCr8Ahz7F-CtwNB93XmFE,23
10
10
  phoenix/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  phoenix/core/embedding_dimension.py,sha256=zKGbcvwOXgLf-yrJBpQyKtd-LEOPRKHnUToyAU8Owis,87
12
12
  phoenix/core/model.py,sha256=km_a--PBHOuA337ClRw9xqhOHhrUT6Rl9pz_zV0JYkQ,4843
@@ -51,7 +51,7 @@ phoenix/experiments/evaluators/llm_evaluators.py,sha256=zyGhxXBDNi1qoj_8I95PRSwj
51
51
  phoenix/experiments/evaluators/utils.py,sha256=XYqB0bOljyR0GewmR_mm9Ndl_q95EkjjDqfXd7YVqTk,9303
52
52
  phoenix/inferences/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  phoenix/inferences/errors.py,sha256=cGp9vxnw4SewFoWBV3ZGMkhE0Kh73lPIv3Ppz_H_RoA,8261
54
- phoenix/inferences/fixtures.py,sha256=r5mN8I58rirO2YqHr_88G2aRC0ADYLSKozgxEG89MTM,20833
54
+ phoenix/inferences/fixtures.py,sha256=oTtfjkI9ULxQ9bKtt91QGSDd3eyJ6T1ZylGiJf1iueo,20892
55
55
  phoenix/inferences/inferences.py,sha256=r-ByeW_AU6cu199iJMn_Td3XywqtRfrLS7cDuHaayUA,31147
56
56
  phoenix/inferences/schema.py,sha256=UYej9IJ6pFeNW3fq721kJy16ONso_xVDm78Q68G4hl4,6643
57
57
  phoenix/inferences/validation.py,sha256=fdmbsjUBwtacRiVFdh9aem-QrgPfq_OlEmPdascWluc,8297
@@ -69,17 +69,17 @@ phoenix/pointcloud/pointcloud.py,sha256=4zAIkKs2xOUbchpj4XDAV-iPMXrfAJ15TG6rlIYG
69
69
  phoenix/pointcloud/projectors.py,sha256=zO_RrtDYSv2rqVOfIP2_9Cv11Dc8EmcZR94xhFcBYPU,1057
70
70
  phoenix/pointcloud/umap_parameters.py,sha256=3UQSjrysVOvq2V4KNpTMqNqNiK0BsTZnPBHWZ4fyJtQ,1708
71
71
  phoenix/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
- phoenix/server/app.py,sha256=KcxSXDR_awZlIcJg4YkUTZIXlje85_dpdMB2OM-M7SQ,22059
72
+ phoenix/server/app.py,sha256=7LiZiusw_yHZBVWMk8gH4tX8yFitTJ2jdyhMWVm2DdM,26817
73
73
  phoenix/server/dml_event.py,sha256=MpjCFqljxvgb9OB5Cez9vJesb3oHb3XxXictynBfcis,2851
74
74
  phoenix/server/dml_event_handler.py,sha256=6p-PucctivelVHfO-_9zNxWZYPr_eGjDF3bKjLtc5co,8251
75
75
  phoenix/server/grpc_server.py,sha256=jllxDNkpLQxDkvej4RhTokobowbvydF-SU8gSw1MTCc,3378
76
- phoenix/server/main.py,sha256=rcXBEcSpnwFHwTZcMIu3s8Y7ABCMEZYJ0xb8wKtBfL0,11731
76
+ phoenix/server/main.py,sha256=KcyiOtU7pJrWASTih4huF53WizXUdjCpWSqY6glk-mA,14037
77
77
  phoenix/server/prometheus.py,sha256=j9DHB2fERuq_ZKmwVaqR-9wx5WcPPuU1Cm5Bhg5241Y,2996
78
78
  phoenix/server/telemetry.py,sha256=T_2OKrxNViAeaANlNspEekg_Y5uZIFWvKAnpz8Aoqvk,2762
79
79
  phoenix/server/thread_server.py,sha256=RwXQGP_QhGD7le6WB7xEygEEuwBl5Ck_Zo8xGIYGi9M,2135
80
80
  phoenix/server/types.py,sha256=S2dReLNboR2nzjRK5j3MUyUDqu6AQFD7KRwJkeKj1q4,3609
81
81
  phoenix/server/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
82
- phoenix/server/api/context.py,sha256=N_kqhQIlRyPS6JpN-fgCZ2U4_ZbhaknH-23g5JoRU8g,3282
82
+ phoenix/server/api/context.py,sha256=WuhGT2549C5Yc7pWj2S7NaPeT4a-N-_mmz-Vg5bUkI8,3637
83
83
  phoenix/server/api/interceptor.py,sha256=ykDnoC_apUd-llVli3m1CW18kNSIgjz2qZ6m5JmPDu8,1294
84
84
  phoenix/server/api/queries.py,sha256=GXQzz1KmRqZCOENemdgoqBlTVA_4V2AZE-g0hBcO0Qk,23839
85
85
  phoenix/server/api/schema.py,sha256=BcxdqO5CSGqpKd-AAJHMjFlzaK9oJA8GJuxmMfcdjn4,434
@@ -139,9 +139,10 @@ phoenix/server/api/input_types/TimeRange.py,sha256=yzx-gxj8mDeGLft1FzU_x1MVEgIG5
139
139
  phoenix/server/api/input_types/TraceAnnotationSort.py,sha256=BzwiUnMh2VsgQYnhDlbJ6ljHugqIS4YDUlYzvq_tl3o,365
140
140
  phoenix/server/api/input_types/UserRoleInput.py,sha256=xxhFe0ITZOgRVEJbVem_W6F1Ip_H6xDENdQqMMx-kKE,129
141
141
  phoenix/server/api/input_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
142
- phoenix/server/api/mutations/__init__.py,sha256=Cu4lPgUFRAGzKO528jKepwKtfre9lkLTN059S2Shmnw,977
142
+ phoenix/server/api/mutations/__init__.py,sha256=JS3WRqYxNeoaLsKjODFvJnZb6CF19IFW-lfOsUq3rtM,1074
143
143
  phoenix/server/api/mutations/api_key_mutations.py,sha256=cv6AT6UAL55lTC_UqMdcN-1TjWAgqqZi__S9Tw12t6I,3688
144
144
  phoenix/server/api/mutations/auth.py,sha256=8o4tTfGCPkpUauuB9ijPH84Od77UX_UrQWfmUsnujI4,524
145
+ phoenix/server/api/mutations/auth_mutations.py,sha256=6--KYnvY3k9JC70Z7iHWhrEa9xKjGH1sW-iOtC13Oxw,2041
145
146
  phoenix/server/api/mutations/dataset_mutations.py,sha256=0feBUW_07FEIx6uzepjxfRVhk5lAck0AkrqS1GVdoF4,27041
146
147
  phoenix/server/api/mutations/experiment_mutations.py,sha256=OXtLYdLA33RGy1MFctfv6ug2sODcDElhJph_J9vkIjk,3157
147
148
  phoenix/server/api/mutations/export_events_mutations.py,sha256=t_wYBxaqvBJYRoHslh3Bmoxmwlzoy0u8SsBKWIKN5hE,4028
@@ -153,18 +154,17 @@ phoenix/server/api/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
153
154
  phoenix/server/api/openapi/main.py,sha256=KNutA_7AvV_WlGX8cOkvvDujcJKQ7AD1HT6rTpCpR8A,616
154
155
  phoenix/server/api/openapi/schema.py,sha256=oVZoflWMfzOrLKMIrjr3iLnJ13rmN-t_DOe9g6KoN5s,471
155
156
  phoenix/server/api/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
156
- phoenix/server/api/routers/auth.py,sha256=dGug0NjOjW1mmIghmheAgHutG7_0-RjL-FcEReWzTHc,1806
157
157
  phoenix/server/api/routers/utils.py,sha256=M41BoH-fl37izhRuN2aX7lWm7jOC20A_3uClv9TVUUY,583
158
158
  phoenix/server/api/routers/v1/__init__.py,sha256=nb49zcOdAi3DSGuC9gUubN9Yri-o7-WFdlGak4jGuFw,1462
159
159
  phoenix/server/api/routers/v1/datasets.py,sha256=l3Hlc9AVyvX5GdT9iOXBsV-i4c_vtnCaXeSAWdNzcw8,37090
160
160
  phoenix/server/api/routers/v1/evaluations.py,sha256=FSfz9MTi8s65F07abDXlb9-y97fDZSYbqsCXpimwO7g,12628
161
161
  phoenix/server/api/routers/v1/experiment_evaluations.py,sha256=RTQnjupjmh07xowjq77ajbuAZhzIEfYxA4ZtECvGwOU,4844
162
162
  phoenix/server/api/routers/v1/experiment_runs.py,sha256=0G7GgGcZv9dzK47tsPp-p4k5O7W4F_aNRrsNuJN7mho,6393
163
- phoenix/server/api/routers/v1/experiments.py,sha256=GeT3Rya4bdaCr6sCf2Vx6fQ_gfMX5XyFHmODCSJiCfU,9951
163
+ phoenix/server/api/routers/v1/experiments.py,sha256=3u275sGuYSiMyzC_obbjK3mf6aYb7SkY2c_wOg3z4xg,11751
164
164
  phoenix/server/api/routers/v1/pydantic_compat.py,sha256=FeK8oe2brqu-djsoqRxiKL4tw5cHmi89OHVfCFxYsAo,2890
165
165
  phoenix/server/api/routers/v1/spans.py,sha256=MAkMLrONFtItQxkHJde_Wpvz0jsgydegxVZOkZkRUsU,8781
166
166
  phoenix/server/api/routers/v1/traces.py,sha256=HJDmYKMATL40dZEJro6uQ3imbCZBzk3nUun9d21jcDs,7799
167
- phoenix/server/api/routers/v1/utils.py,sha256=xvl2v-BKUkqmFVMmgmmWGFKuRBTrUdoiAeT3mCYEE68,3086
167
+ phoenix/server/api/routers/v1/utils.py,sha256=ph2tC3crWewKhzM2JnX-gAelEHfGLxZeFKXHVWrddmI,3086
168
168
  phoenix/server/api/types/Annotation.py,sha256=7Ym7iuVcbwHlw2yIRylz4nATAF_Cm-Z17qcjiooj1cc,751
169
169
  phoenix/server/api/types/AnnotationSummary.py,sha256=8B2LIROqcrPOi8hvYygsblKvSEBfSrysnKOV7F36hgA,1518
170
170
  phoenix/server/api/types/AnnotatorKind.py,sha256=rPgGdbN1Gvc109sGQ_ZH-gfJbp93V9wlarzTEJNtUwI,236
@@ -237,10 +237,10 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
237
237
  phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
238
238
  phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
239
239
  phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
240
- phoenix/server/static/.vite/manifest.json,sha256=5TDpyzP0p7LFbVpIP1n6hAq2Y9BIMRY07AZTk7p3_pA,1929
241
- phoenix/server/static/assets/components-1Ahruijo.js,sha256=wZdgukvLzeGCBdNCLuU-yOra8gsjFIuC9hSEwa7LuDw,187182
242
- phoenix/server/static/assets/index-BEE_RWJx.js,sha256=WrJXGQvMa2pSjnH_xjCwyRmDfm9MVD6FXq0YytraYEI,7461
243
- phoenix/server/static/assets/pages-CFS6mPnW.js,sha256=OLH9XUE7sS_MRSpD7x-CW4PJt981P-C2biAvQ0dO88g,466231
240
+ phoenix/server/static/.vite/manifest.json,sha256=1BcJFFm0NeLAAfZ6r47H6TP_44e5P36X2rORXCQE5e0,1929
241
+ phoenix/server/static/assets/components-BYH03rjA.js,sha256=P4bMCzZcpwxqWV4NaUU4jpmay0GZusDgXbT9t9IUyeM,189918
242
+ phoenix/server/static/assets/index-fqdjNpYm.js,sha256=SDvWOvsXmEJY3350kb2M0bRmC58xw7M3zDZ0MmdneeU,7515
243
+ phoenix/server/static/assets/pages-DnbxgoTK.js,sha256=OLwZw5mVqnO6AelWT6ndr4kDOhf_8l6ezF5buwHBB7E,502087
244
244
  phoenix/server/static/assets/vendor-DxkFTwjz.css,sha256=nZrkr0u6NNElFGvpWHk9GTHeGoibCXCli1bE7mXZGZg,1816
245
245
  phoenix/server/static/assets/vendor-aSQri0vz.js,sha256=x_07SENutKMhtJ9HgFqkQHvwsDTfPkMmzQznY3HY7Zo,1359197
246
246
  phoenix/server/static/assets/vendor-arizeai-CsdcB1NH.js,sha256=VEn7hFJXcHV_DODmeDi9pEpF_D2NQ1bZYewbPe3BhIw,304008
@@ -250,7 +250,7 @@ phoenix/server/static/assets/vendor-three-DwGkEfCM.js,sha256=0D12ZgKzfKCTSdSTKJB
250
250
  phoenix/server/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
251
251
  phoenix/server/templates/index.html,sha256=dAm0IClgJUdT5AOmjZvtgMg8F_xGrRGv95SAkUyx_kg,4325
252
252
  phoenix/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
253
- phoenix/session/client.py,sha256=GzeSHbNQOh5dyzHV90t_cHJpn5VL0vkNmSnaMq-2ug4,32703
253
+ phoenix/session/client.py,sha256=SqnGTinAIiPGhAXFKu95MTiCHJKn4MMgfk2od2wW9s4,33291
254
254
  phoenix/session/data_extractor.py,sha256=gkEM3WWZAlWGMfRgQopAQlid4cSi6GNco-sdrGir0qc,2788
255
255
  phoenix/session/evaluation.py,sha256=3a33ilo-WU0F_Ze26lkCMMwni0GGceSXRRBLvP3CI1o,5352
256
256
  phoenix/session/session.py,sha256=3DSpXj_mlRAKnb9aNUkDjph19SLHx32IJSKYcR3r7cw,26994
@@ -259,7 +259,7 @@ phoenix/trace/attributes.py,sha256=B_OrzVaxZwFkrAFXZyicYoIti1UdUysURsvUS2GyW1U,1
259
259
  phoenix/trace/errors.py,sha256=wB1z8qdPckngdfU-TORToekvg3344oNFAA83_hC2yFY,180
260
260
  phoenix/trace/evaluation_conventions.py,sha256=t8jydM3U0-T5YpiQKRJ3tWdWGlHtzKyttYdw-ddvPOk,1048
261
261
  phoenix/trace/exporter.py,sha256=eAYemdvDCHMugDJiaR29BFFMTQBdf3oerdkz34Cl3hE,4736
262
- phoenix/trace/fixtures.py,sha256=PgawxpyxXz2596gJEj0yU27t-zcphz_ORGsxeAMvjKY,15345
262
+ phoenix/trace/fixtures.py,sha256=EHfqgvPoux6KkckX00WeG2Vhas8H5vqqFBMTztwgV-s,16857
263
263
  phoenix/trace/otel.py,sha256=WA720jvRadiZBAKjsYoPyXzypHwbyEK2OZRVUwtbjB8,9976
264
264
  phoenix/trace/projects.py,sha256=2BwlNjFE-uwpqYtCu5YyBiYZk9wRPpM13vh3-Cv7GkA,2157
265
265
  phoenix/trace/schemas.py,sha256=HpWSyzec0yDHEQXEDuwyLbhpvKrqkGps8BJqGiIFj8Y,5978
@@ -271,7 +271,7 @@ phoenix/trace/utils.py,sha256=1SEQr37cdHOM0P3BdL1dszArj3Zm-VJQyb1BcJs_qO8,1833
271
271
  phoenix/trace/dsl/README.md,sha256=ihmP9zGUC5V-TDbzKla76LuyDqPDQIBUH2BORwxNI68,2902
272
272
  phoenix/trace/dsl/__init__.py,sha256=WIQIjJg362XD3s50OsPJJ0xbDsGp41bSv7vDllLrPuA,144
273
273
  phoenix/trace/dsl/filter.py,sha256=9NwATCUOgJ4Pms8XsEcinROUuxZ9UW-ISV09o65Ms70,32600
274
- phoenix/trace/dsl/helpers.py,sha256=ULAhqWULPqYWCSNX7y50DVKIqfySx86nqb6hDvZPnVk,3896
274
+ phoenix/trace/dsl/helpers.py,sha256=STQtbmF3yI97GM4yH_V--mrGe1JqldUJJc5LO1o4NWo,3919
275
275
  phoenix/trace/dsl/query.py,sha256=W0t-tiXh2WIVb96lzFAGQOQ-U46uKux78d4KL3rW-PE,30316
276
276
  phoenix/trace/langchain/__init__.py,sha256=F37GfD1pd5Kuw7R7iRUM1zXXpO8xEcycNZh5dwqBXNk,109
277
277
  phoenix/trace/langchain/instrumentor.py,sha256=zdh9uZfG7HWna6Wug_agS7MxSbUlfV-nhf3jWFZm61U,1412
@@ -291,8 +291,8 @@ phoenix/utilities/logging.py,sha256=lDXd6EGaamBNcQxL4vP1au9-i_SXe0OraUDiJOcszSw,
291
291
  phoenix/utilities/project.py,sha256=8IJuMM4yUMoooPi37sictGj8Etu9rGmq6RFtc9848cQ,436
292
292
  phoenix/utilities/re.py,sha256=PDve_OLjRTM8yQQJHC8-n3HdIONi7aNils3ZKRZ5uBM,2045
293
293
  phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
294
- arize_phoenix-4.26.0.dist-info/METADATA,sha256=tf_mydvCrJ0GBxa58iEnat5FdCDItFKPN88hnZj7Dqs,12023
295
- arize_phoenix-4.26.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
296
- arize_phoenix-4.26.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
297
- arize_phoenix-4.26.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
298
- arize_phoenix-4.26.0.dist-info/RECORD,,
294
+ arize_phoenix-4.28.1.dist-info/METADATA,sha256=YRglZu4_XOWN3jc_U8mD8QkVN5m1t6me2zuhNiw5iFk,11936
295
+ arize_phoenix-4.28.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
296
+ arize_phoenix-4.28.1.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
297
+ arize_phoenix-4.28.1.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
298
+ arize_phoenix-4.28.1.dist-info/RECORD,,
phoenix/auth.py CHANGED
@@ -39,7 +39,10 @@ def validate_password_format(password: str) -> None:
39
39
  raise ValueError("Password cannot contain whitespace characters")
40
40
  if not password.isascii():
41
41
  raise ValueError("Password can contain only ASCII characters")
42
+ if not len(password) >= MIN_PASSWORD_LENGTH:
43
+ raise ValueError(f"Password must be at least {MIN_PASSWORD_LENGTH} characters long")
42
44
 
43
45
 
44
46
  EMAIL_PATTERN = re.compile(r"^[^@\s]+@[^@\s]+[.][^@\s]+\Z")
45
47
  NUM_ITERATIONS = 10_000
48
+ MIN_PASSWORD_LENGTH = 4
@@ -422,7 +422,7 @@ def get_inferences(
422
422
  Downloads primary and reference inferences for a fixture if they are not found
423
423
  locally.
424
424
  """
425
- fixture = _get_fixture_by_name(fixture_name=fixture_name)
425
+ fixture = get_fixture_by_name(fixture_name=fixture_name)
426
426
  if no_internet:
427
427
  paths = {role: INFERENCES_DIR / path for role, path in fixture.paths()}
428
428
  else:
@@ -436,9 +436,11 @@ def get_inferences(
436
436
  if fixture.reference_file_name is not None:
437
437
  reference_inferences = Inferences(
438
438
  read_parquet(paths[InferencesRole.REFERENCE]),
439
- fixture.reference_schema
440
- if fixture.reference_schema is not None
441
- else fixture.primary_schema,
439
+ (
440
+ fixture.reference_schema
441
+ if fixture.reference_schema is not None
442
+ else fixture.primary_schema
443
+ ),
442
444
  "training",
443
445
  )
444
446
  corpus_inferences = None
@@ -451,10 +453,14 @@ def get_inferences(
451
453
  return primary_inferences, reference_inferences, corpus_inferences
452
454
 
453
455
 
454
- def _get_fixture_by_name(fixture_name: str) -> Fixture:
456
+ def get_fixture_by_name(fixture_name: str) -> Fixture:
455
457
  """
456
- Returns the fixture whose name matches the input name. Raises a ValueError
457
- if the input fixture name does not match any known fixture names.
458
+ Returns the fixture whose name matches the input name.
459
+
460
+ Raises
461
+ ------
462
+ ValueError
463
+ if the input fixture name does not match any known fixture names.
458
464
  """
459
465
  if fixture_name not in NAME_TO_FIXTURE:
460
466
  valid_fixture_names = ", ".join(NAME_TO_FIXTURE.keys())
@@ -496,7 +502,7 @@ def load_example(use_case: str) -> ExampleInferences:
496
502
  reference).
497
503
 
498
504
  """
499
- fixture = _get_fixture_by_name(use_case)
505
+ fixture = get_fixture_by_name(use_case)
500
506
  primary_inferences, reference_inferences, corpus_inferences = get_inferences(use_case)
501
507
  print(f"📥 Loaded {use_case} example datasets.")
502
508
  print("ℹ️ About this use-case:")
@@ -2,6 +2,7 @@ from dataclasses import dataclass
2
2
  from pathlib import Path
3
3
  from typing import Any, Optional
4
4
 
5
+ from starlette.responses import Response as StarletteResponse
5
6
  from strawberry.fastapi import BaseContext
6
7
 
7
8
  from phoenix.core.model_schema import Model
@@ -77,7 +78,7 @@ class Context(BaseContext):
77
78
  secret: Optional[str] = None
78
79
 
79
80
  def get_secret(self) -> str:
80
- """A type safe way to get the application secret. Throws an error if the secret is not set.
81
+ """A type-safe way to get the application secret. Throws an error if the secret is not set.
81
82
 
82
83
  Returns:
83
84
  str: the phoenix secret
@@ -88,3 +89,11 @@ class Context(BaseContext):
88
89
  " Please set the PHOENIX_SECRET environment variable and re-deploy the application."
89
90
  )
90
91
  return self.secret
92
+
93
+ def get_response(self) -> StarletteResponse:
94
+ """
95
+ A type-safe way to get the response object. Throws an error if the response is not set.
96
+ """
97
+ if (response := self.response) is None:
98
+ raise ValueError("no response is set")
99
+ return response
@@ -1,6 +1,7 @@
1
1
  import strawberry
2
2
 
3
3
  from phoenix.server.api.mutations.api_key_mutations import ApiKeyMutationMixin
4
+ from phoenix.server.api.mutations.auth_mutations import AuthMutationMixin
4
5
  from phoenix.server.api.mutations.dataset_mutations import DatasetMutationMixin
5
6
  from phoenix.server.api.mutations.experiment_mutations import ExperimentMutationMixin
6
7
  from phoenix.server.api.mutations.export_events_mutations import ExportEventsMutationMixin
@@ -12,13 +13,14 @@ from phoenix.server.api.mutations.user_mutations import UserMutationMixin
12
13
 
13
14
  @strawberry.type
14
15
  class Mutation(
15
- ProjectMutationMixin,
16
+ ApiKeyMutationMixin,
17
+ AuthMutationMixin,
16
18
  DatasetMutationMixin,
17
19
  ExperimentMutationMixin,
18
20
  ExportEventsMutationMixin,
21
+ ProjectMutationMixin,
19
22
  SpanAnnotationMutationMixin,
20
23
  TraceAnnotationMutationMixin,
21
- ApiKeyMutationMixin,
22
24
  UserMutationMixin,
23
25
  ):
24
26
  pass
@@ -0,0 +1,63 @@
1
+ import asyncio
2
+ from datetime import timedelta
3
+
4
+ import strawberry
5
+ from sqlalchemy import select
6
+ from strawberry.types import Info
7
+
8
+ from phoenix.auth import is_valid_password
9
+ from phoenix.db import models
10
+ from phoenix.server.api.context import Context
11
+ from phoenix.server.api.mutations.auth import HasSecret
12
+
13
+ PHOENIX_ACCESS_TOKEN_COOKIE_NAME = "phoenix-access-token"
14
+ PHOENIX_ACCESS_TOKEN_COOKIE_MAX_AGE_IN_SECONDS = int(timedelta(days=31).total_seconds())
15
+
16
+
17
+ @strawberry.input
18
+ class LoginMutationInput:
19
+ email: str
20
+ password: str
21
+
22
+
23
+ @strawberry.type
24
+ class AuthMutationMixin:
25
+ @strawberry.mutation(permission_classes=[HasSecret]) # type: ignore
26
+ async def login(
27
+ self,
28
+ info: Info[Context, None],
29
+ input: LoginMutationInput,
30
+ ) -> None:
31
+ async with info.context.db() as session:
32
+ if (
33
+ user := await session.scalar(
34
+ select(models.User).where(models.User.email == input.email)
35
+ )
36
+ ) is None or (password_hash := user.password_hash) is None:
37
+ raise ValueError
38
+ secret = info.context.get_secret()
39
+ loop = asyncio.get_running_loop()
40
+ if not await loop.run_in_executor(
41
+ executor=None,
42
+ func=lambda: is_valid_password(
43
+ password=input.password, salt=secret, password_hash=password_hash
44
+ ),
45
+ ):
46
+ raise ValueError
47
+ response = info.context.get_response()
48
+ response.set_cookie(
49
+ key=PHOENIX_ACCESS_TOKEN_COOKIE_NAME,
50
+ value="token", # todo: compute access token
51
+ secure=True,
52
+ httponly=True,
53
+ samesite="strict",
54
+ max_age=PHOENIX_ACCESS_TOKEN_COOKIE_MAX_AGE_IN_SECONDS,
55
+ )
56
+
57
+ @strawberry.mutation(permission_classes=[HasSecret]) # type: ignore
58
+ async def logout(
59
+ self,
60
+ info: Info[Context, None],
61
+ ) -> None:
62
+ response = info.context.get_response()
63
+ response.delete_cookie(key=PHOENIX_ACCESS_TOKEN_COOKIE_NAME)
@@ -1,6 +1,6 @@
1
1
  from datetime import datetime
2
2
  from random import getrandbits
3
- from typing import Any, Dict, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
 
5
5
  from fastapi import APIRouter, HTTPException
6
6
  from pydantic import Field
@@ -252,3 +252,57 @@ async def get_experiment(request: Request, experiment_id: str) -> GetExperimentR
252
252
  updated_at=experiment.updated_at,
253
253
  )
254
254
  )
255
+
256
+
257
+ class ListExperimentsResponseBody(ResponseBody[List[Experiment]]):
258
+ pass
259
+
260
+
261
+ @router.get(
262
+ "/datasets/{dataset_id}/experiments",
263
+ operation_id="listExperiments",
264
+ summary="List experiments by dataset",
265
+ response_description="Experiments retrieved successfully",
266
+ )
267
+ async def list_experiments(
268
+ request: Request,
269
+ dataset_id: str,
270
+ ) -> ListExperimentsResponseBody:
271
+ dataset_gid = GlobalID.from_id(dataset_id)
272
+ try:
273
+ dataset_rowid = from_global_id_with_expected_type(dataset_gid, "Dataset")
274
+ except ValueError:
275
+ raise HTTPException(
276
+ detail=f"Dataset with ID {dataset_gid} does not exist",
277
+ status_code=HTTP_404_NOT_FOUND,
278
+ )
279
+ async with request.app.state.db() as session:
280
+ query = (
281
+ select(models.Experiment)
282
+ .where(models.Experiment.dataset_id == dataset_rowid)
283
+ .order_by(models.Experiment.id.desc())
284
+ )
285
+
286
+ result = await session.execute(query)
287
+ experiments = result.scalars().all()
288
+
289
+ if not experiments:
290
+ return ListExperimentsResponseBody(data=[])
291
+
292
+ data = [
293
+ Experiment(
294
+ id=str(GlobalID("Experiment", str(experiment.id))),
295
+ dataset_id=str(GlobalID("Dataset", str(experiment.dataset_id))),
296
+ dataset_version_id=str(
297
+ GlobalID("DatasetVersion", str(experiment.dataset_version_id))
298
+ ),
299
+ repetitions=experiment.repetitions,
300
+ metadata=experiment.metadata_,
301
+ project_name=None,
302
+ created_at=experiment.created_at,
303
+ updated_at=experiment.updated_at,
304
+ )
305
+ for experiment in experiments
306
+ ]
307
+
308
+ return ListExperimentsResponseBody(data=data)
@@ -1,6 +1,6 @@
1
- from typing import Any, Dict, Generic, List, Optional, TypedDict, Union
1
+ from typing import Any, Dict, Generic, List, Optional, TypedDict, TypeVar, Union
2
2
 
3
- from typing_extensions import TypeAlias, TypeVar, assert_never
3
+ from typing_extensions import TypeAlias, assert_never
4
4
 
5
5
  from .pydantic_compat import V1RoutesBaseModel
6
6