arize-phoenix 4.25.0__py3-none-any.whl → 4.27.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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: arize-phoenix
3
- Version: 4.25.0
3
+ Version: 4.27.0
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
@@ -90,7 +89,7 @@ Requires-Dist: pandas>=1.0; extra == 'dev'
90
89
  Requires-Dist: portpicker; extra == 'dev'
91
90
  Requires-Dist: pre-commit; extra == 'dev'
92
91
  Requires-Dist: prometheus-client; extra == 'dev'
93
- Requires-Dist: psycopg[binary]; extra == 'dev'
92
+ Requires-Dist: psycopg[binary,pool]; extra == 'dev'
94
93
  Requires-Dist: pytest-asyncio; extra == 'dev'
95
94
  Requires-Dist: pytest-cov; extra == 'dev'
96
95
  Requires-Dist: pytest-postgresql; extra == 'dev'
@@ -111,6 +110,7 @@ Requires-Dist: llama-index-readers-file==0.1.25; extra == 'llama-index'
111
110
  Requires-Dist: llama-index==0.10.51; extra == 'llama-index'
112
111
  Provides-Extra: pg
113
112
  Requires-Dist: asyncpg; extra == 'pg'
113
+ Requires-Dist: psycopg[binary,pool]; extra == 'pg'
114
114
  Provides-Extra: test
115
115
  Description-Content-Type: text/markdown
116
116
 
@@ -1,13 +1,12 @@
1
1
  phoenix/__init__.py,sha256=TGNWqm2UW-l67yIRpOtmqGHVAmdoobSNqUsiTtip7uQ,1542
2
+ phoenix/auth.py,sha256=N8vTFmc5BEsdX4xr6Bmh6OwBrNUQykr74LuCIkC28jA,1455
2
3
  phoenix/config.py,sha256=wYA_8GSSz5rnpfIWDjeBL9ehKuTy9jqXaMZnxUqRYEU,10131
3
4
  phoenix/datetime_utils.py,sha256=yDKjwX2Vtqw9h5F_ProtP-TsXidM43uIvmJ_pOzYc9A,3405
4
5
  phoenix/exceptions.py,sha256=n2L2KKuecrdflB9MsCdAYCiSEvGJptIsfRkXMoJle7A,169
5
6
  phoenix/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
6
7
  phoenix/services.py,sha256=OyML4t2XGnlqF0JXA9_uccL8HslTABxep9Ci7MViKEU,5216
7
8
  phoenix/settings.py,sha256=cO-qgis_S27nHirTobYI9hHPfZH18R--WMmxNdsVUwc,273
8
- phoenix/version.py,sha256=gMIFKEThnzM4tu5IZY3JHWVjHbXMgpnlK4Y7S0AWlGs,23
9
- phoenix/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- phoenix/auth/utils.py,sha256=g-97oiJR219P6JvDUU5likHN4CdWtWo7bDJRmfVb0qk,450
9
+ phoenix/version.py,sha256=3tdrXCYXhzGl0HhTFxiRhMv5mTezDVgvqXVYnKeIJeo,23
11
10
  phoenix/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
11
  phoenix/core/embedding_dimension.py,sha256=zKGbcvwOXgLf-yrJBpQyKtd-LEOPRKHnUToyAU8Owis,87
13
12
  phoenix/core/model.py,sha256=km_a--PBHOuA337ClRw9xqhOHhrUT6Rl9pz_zV0JYkQ,4843
@@ -17,7 +16,7 @@ phoenix/db/README.md,sha256=IvKaZyf9ECbGBYYePaRhBveKZwDbxAc-c7BMxJYZh6Q,595
17
16
  phoenix/db/__init__.py,sha256=pDjEFXukHmJBM-1D8RjmXkvLsz85YWNxMQczt81ec3A,118
18
17
  phoenix/db/alembic.ini,sha256=p8DjVqGUs_tTx8oU56JP7qj-rMUebNFizItUSv_hPhs,3763
19
18
  phoenix/db/bulk_inserter.py,sha256=qgg8pt5k4VnHKOE0-KoReXVAfXRhLt-sMZihI-b4X9I,12761
20
- phoenix/db/engines.py,sha256=QDdRE6AM7JHWotAGCCljbaM_tz2hHB3-TSr_F4KUe-Q,5292
19
+ phoenix/db/engines.py,sha256=l9Zl7mPd1q4RTXpThzYzc4Lo7TuuBwaGrC-zK0SMnn4,5300
21
20
  phoenix/db/helpers.py,sha256=2zSc4n5IJfu-CaOFoBfqTB35M1nTFcAc8tqLsNtF2Jw,3488
22
21
  phoenix/db/migrate.py,sha256=NNcci4LHw0wFR7U6quWrA-sw_A4h2lAA1_LePMLkb4w,2629
23
22
  phoenix/db/models.py,sha256=lYWJYtD2asQwKU1B8JKyteWpHVYjhr1j0tmZdf9CQ5Y,23686
@@ -52,7 +51,7 @@ phoenix/experiments/evaluators/llm_evaluators.py,sha256=zyGhxXBDNi1qoj_8I95PRSwj
52
51
  phoenix/experiments/evaluators/utils.py,sha256=XYqB0bOljyR0GewmR_mm9Ndl_q95EkjjDqfXd7YVqTk,9303
53
52
  phoenix/inferences/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
53
  phoenix/inferences/errors.py,sha256=cGp9vxnw4SewFoWBV3ZGMkhE0Kh73lPIv3Ppz_H_RoA,8261
55
- phoenix/inferences/fixtures.py,sha256=r5mN8I58rirO2YqHr_88G2aRC0ADYLSKozgxEG89MTM,20833
54
+ phoenix/inferences/fixtures.py,sha256=oTtfjkI9ULxQ9bKtt91QGSDd3eyJ6T1ZylGiJf1iueo,20892
56
55
  phoenix/inferences/inferences.py,sha256=r-ByeW_AU6cu199iJMn_Td3XywqtRfrLS7cDuHaayUA,31147
57
56
  phoenix/inferences/schema.py,sha256=UYej9IJ6pFeNW3fq721kJy16ONso_xVDm78Q68G4hl4,6643
58
57
  phoenix/inferences/validation.py,sha256=fdmbsjUBwtacRiVFdh9aem-QrgPfq_OlEmPdascWluc,8297
@@ -70,11 +69,11 @@ phoenix/pointcloud/pointcloud.py,sha256=4zAIkKs2xOUbchpj4XDAV-iPMXrfAJ15TG6rlIYG
70
69
  phoenix/pointcloud/projectors.py,sha256=zO_RrtDYSv2rqVOfIP2_9Cv11Dc8EmcZR94xhFcBYPU,1057
71
70
  phoenix/pointcloud/umap_parameters.py,sha256=3UQSjrysVOvq2V4KNpTMqNqNiK0BsTZnPBHWZ4fyJtQ,1708
72
71
  phoenix/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
- phoenix/server/app.py,sha256=Jy_QX0YpX6wSHe9oz45gU3VCaQ18dyyWsOxM-xcOul4,21149
72
+ phoenix/server/app.py,sha256=rPzpaEpTSViIP-RLbHzYfxAf7zplKOEbFHxCb40YXFc,26954
74
73
  phoenix/server/dml_event.py,sha256=MpjCFqljxvgb9OB5Cez9vJesb3oHb3XxXictynBfcis,2851
75
74
  phoenix/server/dml_event_handler.py,sha256=6p-PucctivelVHfO-_9zNxWZYPr_eGjDF3bKjLtc5co,8251
76
75
  phoenix/server/grpc_server.py,sha256=jllxDNkpLQxDkvej4RhTokobowbvydF-SU8gSw1MTCc,3378
77
- phoenix/server/main.py,sha256=rcXBEcSpnwFHwTZcMIu3s8Y7ABCMEZYJ0xb8wKtBfL0,11731
76
+ phoenix/server/main.py,sha256=KcyiOtU7pJrWASTih4huF53WizXUdjCpWSqY6glk-mA,14037
78
77
  phoenix/server/prometheus.py,sha256=j9DHB2fERuq_ZKmwVaqR-9wx5WcPPuU1Cm5Bhg5241Y,2996
79
78
  phoenix/server/telemetry.py,sha256=T_2OKrxNViAeaANlNspEekg_Y5uZIFWvKAnpz8Aoqvk,2762
80
79
  phoenix/server/thread_server.py,sha256=RwXQGP_QhGD7le6WB7xEygEEuwBl5Ck_Zo8xGIYGi9M,2135
@@ -138,8 +137,9 @@ phoenix/server/api/input_types/SpanAnnotationSort.py,sha256=T5pAGzmh4MiJp9JMAzND
138
137
  phoenix/server/api/input_types/SpanSort.py,sha256=Dhvl8BIoV52yHoqntfOax_gUc15uH8ITI_00Ha7PvYc,5959
139
138
  phoenix/server/api/input_types/TimeRange.py,sha256=yzx-gxj8mDeGLft1FzU_x1MVEgIG5Pt6-f8PUVDgipQ,522
140
139
  phoenix/server/api/input_types/TraceAnnotationSort.py,sha256=BzwiUnMh2VsgQYnhDlbJ6ljHugqIS4YDUlYzvq_tl3o,365
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=XFlHUZKD6oJWfnXVXOoP1HYeC7Jdw1TntmAUnOQWn8M,880
142
+ phoenix/server/api/mutations/__init__.py,sha256=Cu4lPgUFRAGzKO528jKepwKtfre9lkLTN059S2Shmnw,977
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
145
  phoenix/server/api/mutations/dataset_mutations.py,sha256=0feBUW_07FEIx6uzepjxfRVhk5lAck0AkrqS1GVdoF4,27041
@@ -148,21 +148,23 @@ phoenix/server/api/mutations/export_events_mutations.py,sha256=t_wYBxaqvBJYRoHsl
148
148
  phoenix/server/api/mutations/project_mutations.py,sha256=MLm7I97lJ85hTuc1tq8sdYA8Ps5WKMV-bGqeeN-Ey90,2279
149
149
  phoenix/server/api/mutations/span_annotations_mutations.py,sha256=DM9gzxrMSAcxwXQ6jNaNGDVgl8oP50LZsBWRYQwLaSo,5955
150
150
  phoenix/server/api/mutations/trace_annotations_mutations.py,sha256=VDiNzX63Agci7WeMbiK-C770JedlC5R7TZVe1UaRhDE,5930
151
+ phoenix/server/api/mutations/user_mutations.py,sha256=uUZ9LEPQAWRxGA4CVHFClHSGpyMlFHwgi6blu3pkuVA,2998
151
152
  phoenix/server/api/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
152
153
  phoenix/server/api/openapi/main.py,sha256=KNutA_7AvV_WlGX8cOkvvDujcJKQ7AD1HT6rTpCpR8A,616
153
154
  phoenix/server/api/openapi/schema.py,sha256=oVZoflWMfzOrLKMIrjr3iLnJ13rmN-t_DOe9g6KoN5s,471
154
155
  phoenix/server/api/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
156
+ phoenix/server/api/routers/auth.py,sha256=dGug0NjOjW1mmIghmheAgHutG7_0-RjL-FcEReWzTHc,1806
155
157
  phoenix/server/api/routers/utils.py,sha256=M41BoH-fl37izhRuN2aX7lWm7jOC20A_3uClv9TVUUY,583
156
158
  phoenix/server/api/routers/v1/__init__.py,sha256=nb49zcOdAi3DSGuC9gUubN9Yri-o7-WFdlGak4jGuFw,1462
157
159
  phoenix/server/api/routers/v1/datasets.py,sha256=l3Hlc9AVyvX5GdT9iOXBsV-i4c_vtnCaXeSAWdNzcw8,37090
158
160
  phoenix/server/api/routers/v1/evaluations.py,sha256=FSfz9MTi8s65F07abDXlb9-y97fDZSYbqsCXpimwO7g,12628
159
161
  phoenix/server/api/routers/v1/experiment_evaluations.py,sha256=RTQnjupjmh07xowjq77ajbuAZhzIEfYxA4ZtECvGwOU,4844
160
162
  phoenix/server/api/routers/v1/experiment_runs.py,sha256=0G7GgGcZv9dzK47tsPp-p4k5O7W4F_aNRrsNuJN7mho,6393
161
- phoenix/server/api/routers/v1/experiments.py,sha256=GeT3Rya4bdaCr6sCf2Vx6fQ_gfMX5XyFHmODCSJiCfU,9951
163
+ phoenix/server/api/routers/v1/experiments.py,sha256=3u275sGuYSiMyzC_obbjK3mf6aYb7SkY2c_wOg3z4xg,11751
162
164
  phoenix/server/api/routers/v1/pydantic_compat.py,sha256=FeK8oe2brqu-djsoqRxiKL4tw5cHmi89OHVfCFxYsAo,2890
163
165
  phoenix/server/api/routers/v1/spans.py,sha256=MAkMLrONFtItQxkHJde_Wpvz0jsgydegxVZOkZkRUsU,8781
164
166
  phoenix/server/api/routers/v1/traces.py,sha256=HJDmYKMATL40dZEJro6uQ3imbCZBzk3nUun9d21jcDs,7799
165
- phoenix/server/api/routers/v1/utils.py,sha256=xvl2v-BKUkqmFVMmgmmWGFKuRBTrUdoiAeT3mCYEE68,3086
167
+ phoenix/server/api/routers/v1/utils.py,sha256=ph2tC3crWewKhzM2JnX-gAelEHfGLxZeFKXHVWrddmI,3086
166
168
  phoenix/server/api/types/Annotation.py,sha256=7Ym7iuVcbwHlw2yIRylz4nATAF_Cm-Z17qcjiooj1cc,751
167
169
  phoenix/server/api/types/AnnotationSummary.py,sha256=8B2LIROqcrPOi8hvYygsblKvSEBfSrysnKOV7F36hgA,1518
168
170
  phoenix/server/api/types/AnnotatorKind.py,sha256=rPgGdbN1Gvc109sGQ_ZH-gfJbp93V9wlarzTEJNtUwI,236
@@ -235,10 +237,10 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
235
237
  phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
236
238
  phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
237
239
  phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
238
- phoenix/server/static/.vite/manifest.json,sha256=5TDpyzP0p7LFbVpIP1n6hAq2Y9BIMRY07AZTk7p3_pA,1929
239
- phoenix/server/static/assets/components-1Ahruijo.js,sha256=wZdgukvLzeGCBdNCLuU-yOra8gsjFIuC9hSEwa7LuDw,187182
240
- phoenix/server/static/assets/index-BEE_RWJx.js,sha256=WrJXGQvMa2pSjnH_xjCwyRmDfm9MVD6FXq0YytraYEI,7461
241
- phoenix/server/static/assets/pages-CFS6mPnW.js,sha256=OLH9XUE7sS_MRSpD7x-CW4PJt981P-C2biAvQ0dO88g,466231
240
+ phoenix/server/static/.vite/manifest.json,sha256=NCacyzu0qbu92qY-iwPA7JHvzK56ZJnp_usNyMD3fZw,1929
241
+ phoenix/server/static/assets/components-1MfQimGx.js,sha256=NfJgri_ChJVeYEExpGOvfr5SLFuX_bZcvjfEVGH3HWI,187209
242
+ phoenix/server/static/assets/index-B263sE2x.js,sha256=PZG-hlU6oncPYh7r6tfdNm5pjuL94SFLHg4fCyr5Oe8,7515
243
+ phoenix/server/static/assets/pages-CqZDVx20.js,sha256=_Lor33vSFj6dZBGvOK4fPt1vXj9pndZFpyyi5BG_8AY,467596
242
244
  phoenix/server/static/assets/vendor-DxkFTwjz.css,sha256=nZrkr0u6NNElFGvpWHk9GTHeGoibCXCli1bE7mXZGZg,1816
243
245
  phoenix/server/static/assets/vendor-aSQri0vz.js,sha256=x_07SENutKMhtJ9HgFqkQHvwsDTfPkMmzQznY3HY7Zo,1359197
244
246
  phoenix/server/static/assets/vendor-arizeai-CsdcB1NH.js,sha256=VEn7hFJXcHV_DODmeDi9pEpF_D2NQ1bZYewbPe3BhIw,304008
@@ -248,7 +250,7 @@ phoenix/server/static/assets/vendor-three-DwGkEfCM.js,sha256=0D12ZgKzfKCTSdSTKJB
248
250
  phoenix/server/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
249
251
  phoenix/server/templates/index.html,sha256=dAm0IClgJUdT5AOmjZvtgMg8F_xGrRGv95SAkUyx_kg,4325
250
252
  phoenix/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
251
- phoenix/session/client.py,sha256=GzeSHbNQOh5dyzHV90t_cHJpn5VL0vkNmSnaMq-2ug4,32703
253
+ phoenix/session/client.py,sha256=SqnGTinAIiPGhAXFKu95MTiCHJKn4MMgfk2od2wW9s4,33291
252
254
  phoenix/session/data_extractor.py,sha256=gkEM3WWZAlWGMfRgQopAQlid4cSi6GNco-sdrGir0qc,2788
253
255
  phoenix/session/evaluation.py,sha256=3a33ilo-WU0F_Ze26lkCMMwni0GGceSXRRBLvP3CI1o,5352
254
256
  phoenix/session/session.py,sha256=3DSpXj_mlRAKnb9aNUkDjph19SLHx32IJSKYcR3r7cw,26994
@@ -257,7 +259,7 @@ phoenix/trace/attributes.py,sha256=B_OrzVaxZwFkrAFXZyicYoIti1UdUysURsvUS2GyW1U,1
257
259
  phoenix/trace/errors.py,sha256=wB1z8qdPckngdfU-TORToekvg3344oNFAA83_hC2yFY,180
258
260
  phoenix/trace/evaluation_conventions.py,sha256=t8jydM3U0-T5YpiQKRJ3tWdWGlHtzKyttYdw-ddvPOk,1048
259
261
  phoenix/trace/exporter.py,sha256=eAYemdvDCHMugDJiaR29BFFMTQBdf3oerdkz34Cl3hE,4736
260
- phoenix/trace/fixtures.py,sha256=PgawxpyxXz2596gJEj0yU27t-zcphz_ORGsxeAMvjKY,15345
262
+ phoenix/trace/fixtures.py,sha256=EHfqgvPoux6KkckX00WeG2Vhas8H5vqqFBMTztwgV-s,16857
261
263
  phoenix/trace/otel.py,sha256=WA720jvRadiZBAKjsYoPyXzypHwbyEK2OZRVUwtbjB8,9976
262
264
  phoenix/trace/projects.py,sha256=2BwlNjFE-uwpqYtCu5YyBiYZk9wRPpM13vh3-Cv7GkA,2157
263
265
  phoenix/trace/schemas.py,sha256=HpWSyzec0yDHEQXEDuwyLbhpvKrqkGps8BJqGiIFj8Y,5978
@@ -269,7 +271,7 @@ phoenix/trace/utils.py,sha256=1SEQr37cdHOM0P3BdL1dszArj3Zm-VJQyb1BcJs_qO8,1833
269
271
  phoenix/trace/dsl/README.md,sha256=ihmP9zGUC5V-TDbzKla76LuyDqPDQIBUH2BORwxNI68,2902
270
272
  phoenix/trace/dsl/__init__.py,sha256=WIQIjJg362XD3s50OsPJJ0xbDsGp41bSv7vDllLrPuA,144
271
273
  phoenix/trace/dsl/filter.py,sha256=9NwATCUOgJ4Pms8XsEcinROUuxZ9UW-ISV09o65Ms70,32600
272
- phoenix/trace/dsl/helpers.py,sha256=ULAhqWULPqYWCSNX7y50DVKIqfySx86nqb6hDvZPnVk,3896
274
+ phoenix/trace/dsl/helpers.py,sha256=STQtbmF3yI97GM4yH_V--mrGe1JqldUJJc5LO1o4NWo,3919
273
275
  phoenix/trace/dsl/query.py,sha256=W0t-tiXh2WIVb96lzFAGQOQ-U46uKux78d4KL3rW-PE,30316
274
276
  phoenix/trace/langchain/__init__.py,sha256=F37GfD1pd5Kuw7R7iRUM1zXXpO8xEcycNZh5dwqBXNk,109
275
277
  phoenix/trace/langchain/instrumentor.py,sha256=zdh9uZfG7HWna6Wug_agS7MxSbUlfV-nhf3jWFZm61U,1412
@@ -289,8 +291,8 @@ phoenix/utilities/logging.py,sha256=lDXd6EGaamBNcQxL4vP1au9-i_SXe0OraUDiJOcszSw,
289
291
  phoenix/utilities/project.py,sha256=8IJuMM4yUMoooPi37sictGj8Etu9rGmq6RFtc9848cQ,436
290
292
  phoenix/utilities/re.py,sha256=PDve_OLjRTM8yQQJHC8-n3HdIONi7aNils3ZKRZ5uBM,2045
291
293
  phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
292
- arize_phoenix-4.25.0.dist-info/METADATA,sha256=zbUCVE7dfjz9AjNV6ZOauoujdQmaAZwXsn02rUz4UhM,11967
293
- arize_phoenix-4.25.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
294
- arize_phoenix-4.25.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
295
- arize_phoenix-4.25.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
296
- arize_phoenix-4.25.0.dist-info/RECORD,,
294
+ arize_phoenix-4.27.0.dist-info/METADATA,sha256=P6671H1bFkfBHk7FF-1pEwq0_9AoN8zR0RRwVJRqZ2w,11936
295
+ arize_phoenix-4.27.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
296
+ arize_phoenix-4.27.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
297
+ arize_phoenix-4.27.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
298
+ arize_phoenix-4.27.0.dist-info/RECORD,,
phoenix/auth.py ADDED
@@ -0,0 +1,45 @@
1
+ import re
2
+ from hashlib import pbkdf2_hmac
3
+
4
+
5
+ def compute_password_hash(*, password: str, salt: str) -> str:
6
+ """
7
+ Salts and hashes a password using PBKDF2, HMAC, and SHA256.
8
+ """
9
+ password_bytes = password.encode("utf-8")
10
+ salt_bytes = salt.encode("utf-8")
11
+ password_hash_bytes = pbkdf2_hmac("sha256", password_bytes, salt_bytes, NUM_ITERATIONS)
12
+ password_hash = password_hash_bytes.hex()
13
+ return password_hash
14
+
15
+
16
+ def is_valid_password(*, password: str, salt: str, password_hash: str) -> bool:
17
+ """
18
+ Determines whether the password is valid by salting and hashing the password
19
+ and comparing against the existing hash value.
20
+ """
21
+ return password_hash == compute_password_hash(password=password, salt=salt)
22
+
23
+
24
+ def validate_email_format(email: str) -> None:
25
+ """
26
+ Checks that the email has a valid format.
27
+ """
28
+ if EMAIL_PATTERN.match(email) is None:
29
+ raise ValueError("Invalid email address")
30
+
31
+
32
+ def validate_password_format(password: str) -> None:
33
+ """
34
+ Checks that the password has a valid format.
35
+ """
36
+ if not password:
37
+ raise ValueError("Password must be non-empty")
38
+ if any(char.isspace() for char in password):
39
+ raise ValueError("Password cannot contain whitespace characters")
40
+ if not password.isascii():
41
+ raise ValueError("Password can contain only ASCII characters")
42
+
43
+
44
+ EMAIL_PATTERN = re.compile(r"^[^@\s]+@[^@\s]+[.][^@\s]+\Z")
45
+ NUM_ITERATIONS = 10_000
phoenix/db/engines.py CHANGED
@@ -139,7 +139,7 @@ def aio_postgresql_engine(
139
139
  if not migrate:
140
140
  return engine
141
141
  sync_engine = sqlalchemy.create_engine(
142
- url=url.set(drivername="postgresql"),
142
+ url=url.set(drivername="postgresql+psycopg"),
143
143
  echo=Settings.log_migrations,
144
144
  json_serializer=_dumps,
145
145
  )
@@ -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:")
@@ -0,0 +1,9 @@
1
+ from enum import Enum
2
+
3
+ import strawberry
4
+
5
+
6
+ @strawberry.enum
7
+ class UserRoleInput(Enum):
8
+ ADMIN = "ADMIN"
9
+ MEMBER = "MEMBER"
@@ -7,6 +7,7 @@ from phoenix.server.api.mutations.export_events_mutations import ExportEventsMut
7
7
  from phoenix.server.api.mutations.project_mutations import ProjectMutationMixin
8
8
  from phoenix.server.api.mutations.span_annotations_mutations import SpanAnnotationMutationMixin
9
9
  from phoenix.server.api.mutations.trace_annotations_mutations import TraceAnnotationMutationMixin
10
+ from phoenix.server.api.mutations.user_mutations import UserMutationMixin
10
11
 
11
12
 
12
13
  @strawberry.type
@@ -18,5 +19,6 @@ class Mutation(
18
19
  SpanAnnotationMutationMixin,
19
20
  TraceAnnotationMutationMixin,
20
21
  ApiKeyMutationMixin,
22
+ UserMutationMixin,
21
23
  ):
22
24
  pass
@@ -0,0 +1,89 @@
1
+ import asyncio
2
+ from typing import Optional
3
+
4
+ import strawberry
5
+ from sqlalchemy import insert, select
6
+ from sqlean.dbapi2 import IntegrityError # type: ignore[import-untyped]
7
+ from strawberry.types import Info
8
+
9
+ from phoenix.auth import compute_password_hash, validate_email_format, validate_password_format
10
+ from phoenix.db import models
11
+ from phoenix.server.api.context import Context
12
+ from phoenix.server.api.input_types.UserRoleInput import UserRoleInput
13
+ from phoenix.server.api.types.User import User
14
+ from phoenix.server.api.types.UserRole import UserRole
15
+
16
+
17
+ @strawberry.input
18
+ class CreateUserInput:
19
+ email: str
20
+ username: Optional[str]
21
+ password: str
22
+ role: UserRoleInput
23
+
24
+
25
+ @strawberry.type
26
+ class UserMutationPayload:
27
+ user: User
28
+
29
+
30
+ @strawberry.type
31
+ class UserMutationMixin:
32
+ @strawberry.mutation
33
+ async def create_user(
34
+ self,
35
+ info: Info[Context, None],
36
+ input: CreateUserInput,
37
+ ) -> UserMutationPayload:
38
+ validate_email_format(email := input.email)
39
+ validate_password_format(password := input.password)
40
+ role_name = input.role.value
41
+ user_role_id = (
42
+ select(models.UserRole.id).where(models.UserRole.name == role_name).scalar_subquery()
43
+ )
44
+ secret = info.context.get_secret()
45
+ loop = asyncio.get_running_loop()
46
+ password_hash = await loop.run_in_executor(
47
+ executor=None,
48
+ func=lambda: compute_password_hash(password=password, salt=secret),
49
+ )
50
+ try:
51
+ async with info.context.db() as session:
52
+ user = await session.scalar(
53
+ insert(models.User)
54
+ .values(
55
+ user_role_id=user_role_id,
56
+ username=input.username,
57
+ email=email,
58
+ auth_method="LOCAL",
59
+ password_hash=password_hash,
60
+ reset_password=True,
61
+ )
62
+ .returning(models.User)
63
+ )
64
+ assert user is not None
65
+ except IntegrityError as error:
66
+ raise ValueError(_get_user_create_error_message(error))
67
+ return UserMutationPayload(
68
+ user=User(
69
+ id_attr=user.id,
70
+ email=user.email,
71
+ username=user.username,
72
+ created_at=user.created_at,
73
+ role=UserRole(id_attr=user.user_role_id, name=role_name),
74
+ )
75
+ )
76
+
77
+
78
+ def _get_user_create_error_message(error: IntegrityError) -> str:
79
+ """
80
+ Gets a user-facing error message to explain why user creation failed.
81
+ """
82
+ original_error_message = str(error)
83
+ username_already_exists = "users.username" in original_error_message
84
+ email_already_exists = "users.email" in original_error_message
85
+ if username_already_exists:
86
+ return "Username already exists"
87
+ elif email_already_exists:
88
+ return "Email already exists"
89
+ return "Failed to create user"
@@ -0,0 +1,52 @@
1
+ import asyncio
2
+ from datetime import timedelta
3
+
4
+ from fastapi import APIRouter, Form, Request, Response
5
+ from sqlalchemy import select
6
+ from starlette.status import HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED
7
+ from typing_extensions import Annotated
8
+
9
+ from phoenix.auth import is_valid_password
10
+ from phoenix.db import models
11
+
12
+ router = APIRouter(include_in_schema=False)
13
+
14
+ PHOENIX_ACCESS_TOKEN_COOKIE_NAME = "phoenix-access-token"
15
+ PHOENIX_ACCESS_TOKEN_COOKIE_MAX_AGE_IN_SECONDS = int(timedelta(days=31).total_seconds())
16
+
17
+
18
+ @router.post("/login")
19
+ async def login(
20
+ request: Request,
21
+ email: Annotated[str, Form()],
22
+ password: Annotated[str, Form()],
23
+ ) -> Response:
24
+ async with request.app.state.db() as session:
25
+ if (
26
+ user := await session.scalar(select(models.User).where(models.User.email == email))
27
+ ) is None or (password_hash := user.password_hash) is None:
28
+ return Response(status_code=HTTP_401_UNAUTHORIZED)
29
+ secret = request.app.state.get_secret()
30
+ loop = asyncio.get_running_loop()
31
+ if not await loop.run_in_executor(
32
+ executor=None,
33
+ func=lambda: is_valid_password(password=password, salt=secret, password_hash=password_hash),
34
+ ):
35
+ return Response(status_code=HTTP_401_UNAUTHORIZED)
36
+ response = Response(status_code=HTTP_204_NO_CONTENT)
37
+ response.set_cookie(
38
+ key=PHOENIX_ACCESS_TOKEN_COOKIE_NAME,
39
+ value="token", # todo: compute access token
40
+ secure=True,
41
+ httponly=True,
42
+ samesite="strict",
43
+ max_age=PHOENIX_ACCESS_TOKEN_COOKIE_MAX_AGE_IN_SECONDS,
44
+ )
45
+ return response
46
+
47
+
48
+ @router.post("/logout")
49
+ async def logout() -> Response:
50
+ response = Response(status_code=HTTP_204_NO_CONTENT)
51
+ response.delete_cookie(key=PHOENIX_ACCESS_TOKEN_COOKIE_NAME)
52
+ return response
@@ -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