arize-phoenix 11.16.1__py3-none-any.whl → 11.18.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.
- {arize_phoenix-11.16.1.dist-info → arize_phoenix-11.18.0.dist-info}/METADATA +2 -2
- {arize_phoenix-11.16.1.dist-info → arize_phoenix-11.18.0.dist-info}/RECORD +26 -26
- phoenix/auth.py +12 -0
- phoenix/config.py +21 -1
- phoenix/db/facilitator.py +4 -1
- phoenix/server/api/mutations/user_mutations.py +7 -3
- phoenix/server/api/queries.py +4 -4
- phoenix/server/api/routers/auth.py +13 -3
- phoenix/server/api/routers/oauth2.py +6 -5
- phoenix/server/api/routers/v1/traces.py +36 -15
- phoenix/server/api/routers/v1/users.py +3 -0
- phoenix/server/static/.vite/manifest.json +51 -45
- phoenix/server/static/assets/{components-CK8hwrPx.js → components-B1Ec5V_g.js} +453 -331
- phoenix/server/static/assets/{index-UY6kXBX7.js → index-DYx39hbu.js} +2 -2
- phoenix/server/static/assets/{pages-D8Hgmz1V.js → pages-B77OHHSB.js} +552 -514
- phoenix/server/static/assets/vendor-BbqekBfb.js +905 -0
- phoenix/server/static/assets/vendor-arizeai-CEwHhYfL.js +168 -0
- phoenix/server/static/assets/vendor-codemirror-CHApHLLJ.js +25 -0
- phoenix/server/static/assets/{vendor-recharts-Cu431IpB.js → vendor-recharts-Bqf7C6Cm.js} +6 -6
- phoenix/server/static/assets/vendor-shiki-BQ88Q1b1.js +5 -0
- phoenix/server/static/assets/{vendor-three-C5WAXd5r.js → vendor-three-BLWp5bic.js} +154 -154
- phoenix/version.py +1 -1
- phoenix/server/static/assets/vendor-_6rG8OMg.js +0 -936
- phoenix/server/static/assets/vendor-arizeai-BznCmJFh.js +0 -168
- phoenix/server/static/assets/vendor-codemirror-29fWLPAy.js +0 -27
- phoenix/server/static/assets/vendor-shiki-Ce9e01lU.js +0 -5
- {arize_phoenix-11.16.1.dist-info → arize_phoenix-11.18.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-11.16.1.dist-info → arize_phoenix-11.18.0.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-11.16.1.dist-info → arize_phoenix-11.18.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-11.16.1.dist-info → arize_phoenix-11.18.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arize-phoenix
|
|
3
|
-
Version: 11.
|
|
3
|
+
Version: 11.18.0
|
|
4
4
|
Summary: AI Observability and Evaluation
|
|
5
5
|
Project-URL: Documentation, https://arize.com/docs/phoenix/
|
|
6
6
|
Project-URL: Issues, https://github.com/Arize-ai/phoenix/issues
|
|
@@ -191,7 +191,7 @@ Install Phoenix via `pip` or `conda`
|
|
|
191
191
|
pip install arize-phoenix
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
Phoenix container images are available via [Docker Hub](https://hub.docker.com/r/arizephoenix/phoenix) and can be deployed using Docker or Kubernetes.
|
|
194
|
+
Phoenix container images are available via [Docker Hub](https://hub.docker.com/r/arizephoenix/phoenix) and can be deployed using Docker or Kubernetes. Arize AI also provides cloud instances at [app.phoenix.arize.com](https://app.phoenix.arize.com/).
|
|
195
195
|
|
|
196
196
|
## Packages
|
|
197
197
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
phoenix/__init__.py,sha256=xkpXH76HFbEDCq8IhiFp-2GnEHx39xPMdOpV5Skew1w,5481
|
|
2
|
-
phoenix/auth.py,sha256=
|
|
3
|
-
phoenix/config.py,sha256=
|
|
2
|
+
phoenix/auth.py,sha256=9rscOefuxy5VPcb6MlOYenuy3gDd5l7RxknONovGslE,11342
|
|
3
|
+
phoenix/config.py,sha256=1Wb3pToeubtw1OFXsLct9HVYxQBsvjTqtPxkCT3q4OA,62749
|
|
4
4
|
phoenix/datetime_utils.py,sha256=pRD-nzxXYKlMWNtd3r2tKGKfPFhwuJhfOAtlGLVAO60,8784
|
|
5
5
|
phoenix/exceptions.py,sha256=n2L2KKuecrdflB9MsCdAYCiSEvGJptIsfRkXMoJle7A,169
|
|
6
6
|
phoenix/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
7
|
phoenix/services.py,sha256=ngkyKGVatX3cO2WJdo2hKdaVKP-xJCMvqthvga6kJss,5196
|
|
8
8
|
phoenix/settings.py,sha256=2kHfT3BNOVd4dAO1bq-syEQbHSG8oX2-7NhOwK2QREk,896
|
|
9
|
-
phoenix/version.py,sha256=
|
|
9
|
+
phoenix/version.py,sha256=674yIbDThxLNMaAj6918CK5bHaDOhKWT8kaou_ywuBk,24
|
|
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=qBFraOtmwCCnWJltKNP18DDG0mULXigytlFsa6YOz6k,4837
|
|
@@ -19,7 +19,7 @@ phoenix/db/bulk_inserter.py,sha256=MbbmKip6gFtnLP1ZA3Z-mP-zy-Qnb9bPD8mRXJQOW2c,1
|
|
|
19
19
|
phoenix/db/constants.py,sha256=-YE2rkzcROG06_rerfnX5hC7fLzOHx1Gjw4nXhX_um4,46
|
|
20
20
|
phoenix/db/engines.py,sha256=tB_8iWMDz0folryVvw29sbBUxJOB2XZ-Xx0Uexj3uns,6889
|
|
21
21
|
phoenix/db/enums.py,sha256=w3O5YuJEEzVTwVDZb8b2UUFhU8yK_GosF081VVrrno0,188
|
|
22
|
-
phoenix/db/facilitator.py,sha256=
|
|
22
|
+
phoenix/db/facilitator.py,sha256=UIC-l14p3R8GFVWPmz04NY-CDm_zAynXCAuIYpj_W_g,20254
|
|
23
23
|
phoenix/db/helpers.py,sha256=dsGONSgkhmVtjMpJh-84KRVTf5uPdQ5c8O2AhUgHkRg,14150
|
|
24
24
|
phoenix/db/migrate.py,sha256=oUrXH8yEbcpL4eh09aSCuUiSrhFli0eT5D_j4ZmYChY,2797
|
|
25
25
|
phoenix/db/models.py,sha256=bxyBRSST8rqBKcAyPyyDHmkv9AadaE3XmQnpcaMvvnk,61588
|
|
@@ -112,7 +112,7 @@ phoenix/server/api/auth.py,sha256=AyYhnZIbY9ALVjg2K6aC2UXSa3Pva5GVDBXyaZ3nD3o,27
|
|
|
112
112
|
phoenix/server/api/context.py,sha256=mqsq_8Ru50e-PxKWNTzh9zptb1PFjYFUf58uW59UYL0,8996
|
|
113
113
|
phoenix/server/api/exceptions.py,sha256=E2W0x63CBzc0CoQPptrLr9nZxPF9zIP8MCJ3RuJMddw,1322
|
|
114
114
|
phoenix/server/api/interceptor.py,sha256=ykDnoC_apUd-llVli3m1CW18kNSIgjz2qZ6m5JmPDu8,1294
|
|
115
|
-
phoenix/server/api/queries.py,sha256
|
|
115
|
+
phoenix/server/api/queries.py,sha256=ExhH6hftJArSKSdVlKOjbwyWMUT7s8nu1YyW_Wnj4vE,46482
|
|
116
116
|
phoenix/server/api/schema.py,sha256=fcs36xQwFF_Qe41_5cWR8wYpDvOrnbcyTeo5WNMbDsA,1702
|
|
117
117
|
phoenix/server/api/subscriptions.py,sha256=U7JZl-FGfsaIhRkIFdeSQLqR7xCS7CY1h-21BOAcaqY,25439
|
|
118
118
|
phoenix/server/api/utils.py,sha256=quCBRcusc6PUq9tJq7M8PgwFZp7nXgVAxtbw8feribY,833
|
|
@@ -245,14 +245,14 @@ phoenix/server/api/mutations/prompt_version_tag_mutations.py,sha256=t77osYb5he2A
|
|
|
245
245
|
phoenix/server/api/mutations/span_annotations_mutations.py,sha256=LQPcODp7-ZobXspjmtLaamyQa8UkTONC_va-ST9r-k8,15015
|
|
246
246
|
phoenix/server/api/mutations/trace_annotations_mutations.py,sha256=zWoMfOMSQZqw7gZl7Le2PRojkDcG_KOiP1iIuqZpZ8Q,11971
|
|
247
247
|
phoenix/server/api/mutations/trace_mutations.py,sha256=AvtQAfqNWBQpJOZm4e0DZFimhVJ6HQHtSSZtezRadCo,4698
|
|
248
|
-
phoenix/server/api/mutations/user_mutations.py,sha256=
|
|
248
|
+
phoenix/server/api/mutations/user_mutations.py,sha256=6mwMx5I5c_VgUutOLrN9LSMcyPLgXzNaT0XT24ZwNWM,15473
|
|
249
249
|
phoenix/server/api/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
250
250
|
phoenix/server/api/openapi/main.py,sha256=yKdzJYI4cxy_1mFcK4_7YObIcuRviBIfwNjB23RG14k,461
|
|
251
251
|
phoenix/server/api/openapi/schema.py,sha256=WGmHWSIyJhtc5EIh_M3vlXU-EgHkFuTlyVofgS0kj1I,529
|
|
252
252
|
phoenix/server/api/routers/__init__.py,sha256=YIzHsIFOOXuCRbDkMUHx-McrANFJK5UfUn6a4BNIzmo,277
|
|
253
|
-
phoenix/server/api/routers/auth.py,sha256=
|
|
253
|
+
phoenix/server/api/routers/auth.py,sha256=PKGwWdw7O015KmjMY1mIxlvXeU7OrmqIPF5TWTOGmp4,11871
|
|
254
254
|
phoenix/server/api/routers/embeddings.py,sha256=BpZGJee0pdL0W5Rp1L0b30dEtZTgJeVqXky8LgZ0ZXw,898
|
|
255
|
-
phoenix/server/api/routers/oauth2.py,sha256=
|
|
255
|
+
phoenix/server/api/routers/oauth2.py,sha256=rPcKFvfijzBYLjfwbCNzCn0ihn4wGWh4xh6BRqg9Ay4,24524
|
|
256
256
|
phoenix/server/api/routers/utils.py,sha256=M41BoH-fl37izhRuN2aX7lWm7jOC20A_3uClv9TVUUY,583
|
|
257
257
|
phoenix/server/api/routers/v1/__init__.py,sha256=ngLMPjC7lgZxgKy_Is33KxTRnMzSqy25qTTChCVx_Mo,2696
|
|
258
258
|
phoenix/server/api/routers/v1/annotation_configs.py,sha256=xp5lJmKYlRsINCUrRD9-lTAElw2v4hdFndS5BWrxICA,16048
|
|
@@ -266,8 +266,8 @@ phoenix/server/api/routers/v1/models.py,sha256=p3gJN-9SWiUYTUTft4bZMsZVCBNTb4nN1
|
|
|
266
266
|
phoenix/server/api/routers/v1/projects.py,sha256=32GwTLsaFgQLVNdjrlrGe90XT3pIX1N7-zX9D9_J_4w,12701
|
|
267
267
|
phoenix/server/api/routers/v1/prompts.py,sha256=chRYcLkOYDJdJfVZVukVTUyIRnLPvsJCg41CuPxOIU8,26695
|
|
268
268
|
phoenix/server/api/routers/v1/spans.py,sha256=ETH6I14O_zY9IW69Fo-LxL796BR3xgt8qdzwqzYAvbE,44208
|
|
269
|
-
phoenix/server/api/routers/v1/traces.py,sha256=
|
|
270
|
-
phoenix/server/api/routers/v1/users.py,sha256=
|
|
269
|
+
phoenix/server/api/routers/v1/traces.py,sha256=Skn0N_L4ZjoJ7x76PBrqvbKPFiAk8xSe1yxfiOaQ0Gc,11285
|
|
270
|
+
phoenix/server/api/routers/v1/users.py,sha256=eO8zMtGU33Td2_G1l9D7Z0a4CG1CwBUCj_Z9z2uk7wg,12089
|
|
271
271
|
phoenix/server/api/routers/v1/utils.py,sha256=oXIOGPzPTkE0ZWUTRCoRIQQ7wTzoSwtWFaUSjlGBqts,4960
|
|
272
272
|
phoenix/server/api/types/Annotation.py,sha256=gsl8CwjIbDUbZRj4d9USwZ_w_Tkz4i7zuZh9ftV80jA,1132
|
|
273
273
|
phoenix/server/api/types/AnnotationConfig.py,sha256=TPukZUgvFC17W93Vnme21EhswasBMR-ZiuSWteiWZOU,3891
|
|
@@ -391,17 +391,17 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
|
|
|
391
391
|
phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
|
|
392
392
|
phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
|
|
393
393
|
phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
|
|
394
|
-
phoenix/server/static/.vite/manifest.json,sha256=
|
|
395
|
-
phoenix/server/static/assets/components-
|
|
396
|
-
phoenix/server/static/assets/index-
|
|
397
|
-
phoenix/server/static/assets/pages-
|
|
394
|
+
phoenix/server/static/.vite/manifest.json,sha256=wdIctWiLSvBXLW9VyoDGwL1JHv4egHSTv5LoL920QKU,2328
|
|
395
|
+
phoenix/server/static/assets/components-B1Ec5V_g.js,sha256=4acB1c9xZ91quZtIby2N2jN-wX4GCA5G3RnlmW9FKwc,656940
|
|
396
|
+
phoenix/server/static/assets/index-DYx39hbu.js,sha256=1xQZOioOvWwZOZfyjmyCV_TejQqY3C_CH2mN609RA-Q,63064
|
|
397
|
+
phoenix/server/static/assets/pages-B77OHHSB.js,sha256=oQjHFiCc5ciitUdp0-LtGBshV-ZIIh0QxvSmZr0Sx5E,1218398
|
|
398
|
+
phoenix/server/static/assets/vendor-BbqekBfb.js,sha256=8xINQdH4ikfrf8nr8mlO0B9YrKJ2FPecrA9qu5kPILo,2588857
|
|
398
399
|
phoenix/server/static/assets/vendor-CqDb5u4o.css,sha256=zIyFiNJKxMaQk8AvtLgt1rR01oO10d1MFndSDKH9Clw,5517
|
|
399
|
-
phoenix/server/static/assets/vendor-
|
|
400
|
-
phoenix/server/static/assets/vendor-
|
|
401
|
-
phoenix/server/static/assets/vendor-
|
|
402
|
-
phoenix/server/static/assets/vendor-
|
|
403
|
-
phoenix/server/static/assets/vendor-
|
|
404
|
-
phoenix/server/static/assets/vendor-three-C5WAXd5r.js,sha256=ELkg06u70N7h8oFmvqdoHyPuUf9VgGEWeT4LKFx4VWo,620975
|
|
400
|
+
phoenix/server/static/assets/vendor-arizeai-CEwHhYfL.js,sha256=EIl1d9G6uPn7_Fc8YyAdxWmyV1Y7k1nN7VeJmI4MxtA,121514
|
|
401
|
+
phoenix/server/static/assets/vendor-codemirror-CHApHLLJ.js,sha256=q9KKVgYlf8hMPLO3915yxYrRr-vW7abf22fLQcn7CEA,402623
|
|
402
|
+
phoenix/server/static/assets/vendor-recharts-Bqf7C6Cm.js,sha256=MsKqIXTx4JHHa9EtfFlQ_QqHZOfc6ETZDxAP7sJlzoQ,231651
|
|
403
|
+
phoenix/server/static/assets/vendor-shiki-BQ88Q1b1.js,sha256=5oAj5_AuRlfSv2GHNstkg5klTm5gj_WnPa8dJ_B5ReA,305160
|
|
404
|
+
phoenix/server/static/assets/vendor-three-BLWp5bic.js,sha256=vfSCVXS20jA0Ceo_O0mDxYBcROinWMdPE6RR4JXmtec,620972
|
|
405
405
|
phoenix/server/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
406
406
|
phoenix/server/templates/index.html,sha256=0sqTnrrfw-BxrXaj2PayCgOUu-MseC6eUGBJwZx7KO8,7116
|
|
407
407
|
phoenix/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -441,9 +441,9 @@ phoenix/utilities/project.py,sha256=auVpARXkDb-JgeX5f2aStyFIkeKvGwN9l7qrFeJMVxI,
|
|
|
441
441
|
phoenix/utilities/re.py,sha256=6YyUWIkv0zc2SigsxfOWIHzdpjKA_TZo2iqKq7zJKvw,2081
|
|
442
442
|
phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
443
443
|
phoenix/utilities/template_formatters.py,sha256=gh9PJD6WEGw7TEYXfSst1UR4pWWwmjxMLrDVQ_CkpkQ,2779
|
|
444
|
-
arize_phoenix-11.
|
|
445
|
-
arize_phoenix-11.
|
|
446
|
-
arize_phoenix-11.
|
|
447
|
-
arize_phoenix-11.
|
|
448
|
-
arize_phoenix-11.
|
|
449
|
-
arize_phoenix-11.
|
|
444
|
+
arize_phoenix-11.18.0.dist-info/METADATA,sha256=XaPsPlJWMGjRNAAtWtBTxz1RuLSV0A_U37UeL1u-1wI,30950
|
|
445
|
+
arize_phoenix-11.18.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
446
|
+
arize_phoenix-11.18.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
|
|
447
|
+
arize_phoenix-11.18.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
|
|
448
|
+
arize_phoenix-11.18.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
|
|
449
|
+
arize_phoenix-11.18.0.dist-info/RECORD,,
|
phoenix/auth.py
CHANGED
|
@@ -47,6 +47,18 @@ def is_valid_password(*, password: Secret, salt: bytes, password_hash: bytes) ->
|
|
|
47
47
|
return password_hash == compute_password_hash(password=password, salt=salt)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def sanitize_email(email: str) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Sanitizes an email address by trimming whitespace and converting to lowercase.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
email (str): the email address to sanitize
|
|
56
|
+
Returns:
|
|
57
|
+
str: the sanitized email address
|
|
58
|
+
"""
|
|
59
|
+
return email.strip().lower()
|
|
60
|
+
|
|
61
|
+
|
|
50
62
|
def validate_email_format(email: str) -> None:
|
|
51
63
|
"""
|
|
52
64
|
Checks that the email has a valid format.
|
phoenix/config.py
CHANGED
|
@@ -333,6 +333,10 @@ Whether to verify client certificates for mutual TLS (mTLS) authentication.
|
|
|
333
333
|
When set to true, clients must provide valid certificates signed by the CA specified in
|
|
334
334
|
PHOENIX_TLS_CA_FILE.
|
|
335
335
|
"""
|
|
336
|
+
ENV_PHOENIX_DEFAULT_RETENTION_POLICY_DAYS = "PHOENIX_DEFAULT_RETENTION_POLICY_DAYS"
|
|
337
|
+
"""
|
|
338
|
+
The default retention policy for traces in days.
|
|
339
|
+
"""
|
|
336
340
|
|
|
337
341
|
|
|
338
342
|
@dataclass(frozen=True)
|
|
@@ -494,6 +498,20 @@ def get_env_tls_verify_client() -> bool:
|
|
|
494
498
|
return _bool_val(ENV_PHOENIX_TLS_VERIFY_CLIENT, False)
|
|
495
499
|
|
|
496
500
|
|
|
501
|
+
def get_env_default_retention_policy_days() -> int:
|
|
502
|
+
"""
|
|
503
|
+
Returns the number of days for the default retention policy as set by the
|
|
504
|
+
PHOENIX_DEFAULT_RETENTION_POLICY_DAYS environment variable, defaulting to 0 if not set.
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
int: Number of days for the default retention policy. Defaults to 0 if the environment variable is not set.
|
|
508
|
+
""" # noqa: E501
|
|
509
|
+
days = _int_val(ENV_PHOENIX_DEFAULT_RETENTION_POLICY_DAYS, 0)
|
|
510
|
+
if days < 0:
|
|
511
|
+
raise ValueError("PHOENIX_DEFAULT_RETENTION_POLICY_DAYS must be non-negative")
|
|
512
|
+
return days
|
|
513
|
+
|
|
514
|
+
|
|
497
515
|
def get_env_tls_config() -> Optional[TLSConfig]:
|
|
498
516
|
"""
|
|
499
517
|
Retrieves and validates TLS configuration from environment variables.
|
|
@@ -865,6 +883,8 @@ def get_env_admins() -> dict[str, str]:
|
|
|
865
883
|
"""
|
|
866
884
|
if not (env_value := getenv(ENV_PHOENIX_ADMINS)):
|
|
867
885
|
return {}
|
|
886
|
+
from phoenix.auth import sanitize_email
|
|
887
|
+
|
|
868
888
|
usernames = set()
|
|
869
889
|
emails = set()
|
|
870
890
|
ans = {}
|
|
@@ -881,7 +901,7 @@ def get_env_admins() -> dict[str, str]:
|
|
|
881
901
|
f"Expected format: 'username=email'"
|
|
882
902
|
)
|
|
883
903
|
username = pair[:last_equals_pos].strip()
|
|
884
|
-
email_addr = pair[last_equals_pos + 1 :]
|
|
904
|
+
email_addr = sanitize_email(pair[last_equals_pos + 1 :])
|
|
885
905
|
try:
|
|
886
906
|
email_addr = validate_email(email_addr, check_deliverability=False).normalized
|
|
887
907
|
except EmailNotValidError:
|
phoenix/db/facilitator.py
CHANGED
|
@@ -28,6 +28,7 @@ from phoenix.auth import (
|
|
|
28
28
|
from phoenix.config import (
|
|
29
29
|
get_env_admins,
|
|
30
30
|
get_env_default_admin_initial_password,
|
|
31
|
+
get_env_default_retention_policy_days,
|
|
31
32
|
get_env_disable_basic_auth,
|
|
32
33
|
)
|
|
33
34
|
from phoenix.db import models
|
|
@@ -334,7 +335,9 @@ async def _ensure_default_project_trace_retention_policy(db: DbSessionFactory) -
|
|
|
334
335
|
):
|
|
335
336
|
return
|
|
336
337
|
cron_expression = TraceRetentionCronExpression(root="0 0 * * 0")
|
|
337
|
-
rule = TraceRetentionRule(
|
|
338
|
+
rule = TraceRetentionRule(
|
|
339
|
+
root=MaxDaysRule(max_days=get_env_default_retention_policy_days())
|
|
340
|
+
)
|
|
338
341
|
await session.execute(
|
|
339
342
|
sa.insert(models.ProjectTraceRetentionPolicy),
|
|
340
343
|
[
|
|
@@ -21,6 +21,7 @@ from phoenix.auth import (
|
|
|
21
21
|
PASSWORD_REQUIREMENTS,
|
|
22
22
|
PHOENIX_ACCESS_TOKEN_COOKIE_NAME,
|
|
23
23
|
PHOENIX_REFRESH_TOKEN_COOKIE_NAME,
|
|
24
|
+
sanitize_email,
|
|
24
25
|
validate_email_format,
|
|
25
26
|
validate_password_format,
|
|
26
27
|
)
|
|
@@ -115,20 +116,23 @@ class UserMutationMixin:
|
|
|
115
116
|
info: Info[Context, None],
|
|
116
117
|
input: CreateUserInput,
|
|
117
118
|
) -> UserMutationPayload:
|
|
119
|
+
# Sanitize email by trimming and lowercasing
|
|
120
|
+
email = sanitize_email(input.email)
|
|
121
|
+
|
|
118
122
|
user: models.User
|
|
119
123
|
if input.auth_method is AuthMethod.OAUTH2:
|
|
120
124
|
user = models.OAuth2User(
|
|
121
|
-
email=
|
|
125
|
+
email=email,
|
|
122
126
|
username=input.username,
|
|
123
127
|
)
|
|
124
128
|
else:
|
|
125
129
|
assert input.password
|
|
126
|
-
validate_email_format(
|
|
130
|
+
validate_email_format(email)
|
|
127
131
|
validate_password_format(input.password)
|
|
128
132
|
salt = secrets.token_bytes(DEFAULT_SECRET_LENGTH)
|
|
129
133
|
password_hash = await info.context.hash_password(Secret(input.password), salt)
|
|
130
134
|
user = models.LocalUser(
|
|
131
|
-
email=
|
|
135
|
+
email=email,
|
|
132
136
|
username=input.username,
|
|
133
137
|
password_hash=password_hash,
|
|
134
138
|
password_salt=salt,
|
phoenix/server/api/queries.py
CHANGED
|
@@ -338,19 +338,19 @@ class Query:
|
|
|
338
338
|
async def compare_experiments(
|
|
339
339
|
self,
|
|
340
340
|
info: Info[Context, None],
|
|
341
|
-
|
|
341
|
+
base_experiment_id: GlobalID,
|
|
342
342
|
compare_experiment_ids: list[GlobalID],
|
|
343
343
|
first: Optional[int] = 50,
|
|
344
344
|
after: Optional[CursorString] = UNSET,
|
|
345
345
|
filter_condition: Optional[str] = UNSET,
|
|
346
346
|
) -> Connection[ExperimentComparison]:
|
|
347
|
-
if
|
|
348
|
-
raise BadRequest("Compare experiment IDs cannot contain the
|
|
347
|
+
if base_experiment_id in compare_experiment_ids:
|
|
348
|
+
raise BadRequest("Compare experiment IDs cannot contain the base experiment ID")
|
|
349
349
|
if len(set(compare_experiment_ids)) < len(compare_experiment_ids):
|
|
350
350
|
raise BadRequest("Compare experiment IDs must be unique")
|
|
351
351
|
experiment_ids = [
|
|
352
352
|
from_global_id_with_expected_type(experiment_id, models.Experiment.__name__)
|
|
353
|
-
for experiment_id in (
|
|
353
|
+
for experiment_id in (base_experiment_id, *compare_experiment_ids)
|
|
354
354
|
]
|
|
355
355
|
cursor = Cursor.from_string(after) if after else None
|
|
356
356
|
page_size = first or 50
|
|
@@ -6,7 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from urllib.parse import urlencode, urlparse, urlunparse
|
|
7
7
|
|
|
8
8
|
from fastapi import APIRouter, Depends, HTTPException, Request, Response
|
|
9
|
-
from sqlalchemy import select
|
|
9
|
+
from sqlalchemy import func, select
|
|
10
10
|
from sqlalchemy.orm import joinedload
|
|
11
11
|
from starlette.status import (
|
|
12
12
|
HTTP_204_NO_CONTENT,
|
|
@@ -29,6 +29,7 @@ from phoenix.auth import (
|
|
|
29
29
|
delete_oauth2_state_cookie,
|
|
30
30
|
delete_refresh_token_cookie,
|
|
31
31
|
is_valid_password,
|
|
32
|
+
sanitize_email,
|
|
32
33
|
set_access_token_cookie,
|
|
33
34
|
set_refresh_token_cookie,
|
|
34
35
|
validate_password_format,
|
|
@@ -87,9 +88,14 @@ async def login(request: Request) -> Response:
|
|
|
87
88
|
if not email or not password:
|
|
88
89
|
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Email and password required")
|
|
89
90
|
|
|
91
|
+
# Sanitize email by trimming and lowercasing
|
|
92
|
+
email = sanitize_email(email)
|
|
93
|
+
|
|
90
94
|
async with request.app.state.db() as session:
|
|
91
95
|
user = await session.scalar(
|
|
92
|
-
select(models.User)
|
|
96
|
+
select(models.User)
|
|
97
|
+
.where(func.lower(models.User.email) == email)
|
|
98
|
+
.options(joinedload(models.User.role))
|
|
93
99
|
)
|
|
94
100
|
if (
|
|
95
101
|
user is None
|
|
@@ -207,6 +213,10 @@ async def initiate_password_reset(request: Request) -> Response:
|
|
|
207
213
|
data = await request.json()
|
|
208
214
|
if not (email := data.get("email")):
|
|
209
215
|
raise MISSING_EMAIL
|
|
216
|
+
|
|
217
|
+
# Sanitize email by trimming and lowercasing
|
|
218
|
+
email = sanitize_email(email)
|
|
219
|
+
|
|
210
220
|
sender: EmailSender = request.app.state.email_sender
|
|
211
221
|
if sender is None:
|
|
212
222
|
raise SMTP_UNAVAILABLE
|
|
@@ -214,7 +224,7 @@ async def initiate_password_reset(request: Request) -> Response:
|
|
|
214
224
|
async with request.app.state.db() as session:
|
|
215
225
|
user = await session.scalar(
|
|
216
226
|
select(models.User)
|
|
217
|
-
.
|
|
227
|
+
.where(func.lower(models.User.email) == email)
|
|
218
228
|
.options(
|
|
219
229
|
joinedload(models.User.password_reset_token).load_only(models.PasswordResetToken.id)
|
|
220
230
|
)
|
|
@@ -27,6 +27,7 @@ from phoenix.auth import (
|
|
|
27
27
|
PHOENIX_OAUTH2_STATE_COOKIE_NAME,
|
|
28
28
|
delete_oauth2_nonce_cookie,
|
|
29
29
|
delete_oauth2_state_cookie,
|
|
30
|
+
sanitize_email,
|
|
30
31
|
set_access_token_cookie,
|
|
31
32
|
set_oauth2_nonce_cookie,
|
|
32
33
|
set_oauth2_state_cookie,
|
|
@@ -217,7 +218,7 @@ class UserInfo:
|
|
|
217
218
|
if not (idp_user_id := (self.idp_user_id or "").strip()):
|
|
218
219
|
raise ValueError("idp_user_id cannot be empty")
|
|
219
220
|
object.__setattr__(self, "idp_user_id", idp_user_id)
|
|
220
|
-
if not (email := (self.email or "")
|
|
221
|
+
if not (email := sanitize_email(self.email or "")):
|
|
221
222
|
raise ValueError("email cannot be empty")
|
|
222
223
|
object.__setattr__(self, "email", email)
|
|
223
224
|
if username := (self.username or "").strip():
|
|
@@ -356,7 +357,7 @@ async def _get_existing_oauth2_user(
|
|
|
356
357
|
- User has a password set
|
|
357
358
|
- User has mismatched OAuth2 credentials
|
|
358
359
|
""" # noqa: E501
|
|
359
|
-
if not (email := (user_info.email or "")
|
|
360
|
+
if not (email := sanitize_email(user_info.email or "")):
|
|
360
361
|
raise ValueError("Email is required.")
|
|
361
362
|
if not (oauth2_user_id := (user_info.idp_user_id or "").strip()):
|
|
362
363
|
raise ValueError("OAuth2 user ID is required.")
|
|
@@ -370,7 +371,7 @@ async def _get_existing_oauth2_user(
|
|
|
370
371
|
if email and email != user.email:
|
|
371
372
|
user.email = email
|
|
372
373
|
else:
|
|
373
|
-
user = await session.scalar(stmt.
|
|
374
|
+
user = await session.scalar(stmt.where(func.lower(models.User.email) == email))
|
|
374
375
|
if user is None or not isinstance(user, models.OAuth2User):
|
|
375
376
|
raise SignInNotAllowed("Sign in is not allowed.")
|
|
376
377
|
# Case 1: Different OAuth2 client - update both client and user IDs
|
|
@@ -520,7 +521,7 @@ async def _email_and_username_exist(
|
|
|
520
521
|
select(
|
|
521
522
|
cast(
|
|
522
523
|
func.coalesce(
|
|
523
|
-
func.max(case((models.User.email == email, 1), else_=0)),
|
|
524
|
+
func.max(case((func.lower(models.User.email) == email, 1), else_=0)),
|
|
524
525
|
0,
|
|
525
526
|
),
|
|
526
527
|
Boolean,
|
|
@@ -532,7 +533,7 @@ async def _email_and_username_exist(
|
|
|
532
533
|
),
|
|
533
534
|
Boolean,
|
|
534
535
|
).label("username_exists"),
|
|
535
|
-
).where(or_(models.User.email == email, models.User.username == username))
|
|
536
|
+
).where(or_(func.lower(models.User.email) == email, models.User.username == username))
|
|
536
537
|
)
|
|
537
538
|
).all()
|
|
538
539
|
return email_exists, username_exists
|
|
@@ -24,6 +24,7 @@ from strawberry.relay import GlobalID
|
|
|
24
24
|
from phoenix.db import models
|
|
25
25
|
from phoenix.db.insertion.helpers import as_kv
|
|
26
26
|
from phoenix.db.insertion.types import Precursors
|
|
27
|
+
from phoenix.server.api.types.node import from_global_id_with_expected_type
|
|
27
28
|
from phoenix.server.authorization import is_not_locked
|
|
28
29
|
from phoenix.server.bearer_auth import PhoenixUser
|
|
29
30
|
from phoenix.server.dml_event import SpanDeleteEvent, TraceAnnotationInsertEvent
|
|
@@ -228,11 +229,13 @@ async def _add_spans(req: ExportTraceServiceRequest, state: State) -> None:
|
|
|
228
229
|
|
|
229
230
|
|
|
230
231
|
@router.delete(
|
|
231
|
-
"/traces/{
|
|
232
|
+
"/traces/{trace_identifier}",
|
|
232
233
|
operation_id="deleteTrace",
|
|
233
|
-
summary="Delete a trace by
|
|
234
|
+
summary="Delete a trace by identifier",
|
|
234
235
|
description=(
|
|
235
|
-
"Delete an entire trace by its
|
|
236
|
+
"Delete an entire trace by its identifier. The identifier can be either:\n"
|
|
237
|
+
"1. A Relay node ID (base64-encoded)\n"
|
|
238
|
+
"2. An OpenTelemetry trace_id (hex string)\n\n"
|
|
236
239
|
"This will permanently remove all spans in the trace and their associated data."
|
|
237
240
|
),
|
|
238
241
|
responses=add_errors_to_responses([HTTP_404_NOT_FOUND]),
|
|
@@ -240,33 +243,51 @@ async def _add_spans(req: ExportTraceServiceRequest, state: State) -> None:
|
|
|
240
243
|
)
|
|
241
244
|
async def delete_trace(
|
|
242
245
|
request: Request,
|
|
243
|
-
|
|
246
|
+
trace_identifier: str = Path(
|
|
247
|
+
description="The trace identifier: either a relay GlobalID or OpenTelemetry trace_id"
|
|
248
|
+
),
|
|
244
249
|
) -> None:
|
|
245
250
|
"""
|
|
246
|
-
Delete a trace by trace_id.
|
|
251
|
+
Delete a trace by identifier (relay GlobalID or OpenTelemetry trace_id).
|
|
247
252
|
|
|
248
253
|
This endpoint will:
|
|
249
|
-
1.
|
|
250
|
-
2.
|
|
251
|
-
3.
|
|
254
|
+
1. Delete the trace by identifier (relay GlobalID or OpenTelemetry trace_id)
|
|
255
|
+
2. Get project_id from the deletion for cache invalidation
|
|
256
|
+
3. Trigger cache invalidation events
|
|
257
|
+
4. Return 204 No Content on success
|
|
252
258
|
|
|
253
259
|
Note: This deletes the entire trace, including all spans, which maintains data consistency
|
|
254
260
|
and avoids orphaned spans or inconsistent cached cumulative fields.
|
|
255
261
|
"""
|
|
256
262
|
async with request.app.state.db() as session:
|
|
257
|
-
#
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
+
# Try to parse as GlobalID first, then fall back to trace_id
|
|
264
|
+
try:
|
|
265
|
+
trace_rowid = from_global_id_with_expected_type(
|
|
266
|
+
GlobalID.from_id(trace_identifier),
|
|
267
|
+
"Trace",
|
|
268
|
+
)
|
|
269
|
+
# Delete by database rowid
|
|
270
|
+
delete_stmt = (
|
|
271
|
+
delete(models.Trace)
|
|
272
|
+
.where(models.Trace.id == trace_rowid)
|
|
273
|
+
.returning(models.Trace.project_rowid)
|
|
274
|
+
)
|
|
275
|
+
error_detail = f"Trace with relay ID '{trace_identifier}' not found"
|
|
276
|
+
except Exception:
|
|
277
|
+
# Delete by OpenTelemetry trace_id
|
|
278
|
+
delete_stmt = (
|
|
279
|
+
delete(models.Trace)
|
|
280
|
+
.where(models.Trace.trace_id == trace_identifier)
|
|
281
|
+
.returning(models.Trace.project_rowid)
|
|
282
|
+
)
|
|
283
|
+
error_detail = f"Trace with trace_id '{trace_identifier}' not found"
|
|
263
284
|
|
|
264
285
|
project_id = await session.scalar(delete_stmt)
|
|
265
286
|
|
|
266
287
|
if project_id is None:
|
|
267
288
|
raise HTTPException(
|
|
268
289
|
status_code=HTTP_404_NOT_FOUND,
|
|
269
|
-
detail=
|
|
290
|
+
detail=error_detail,
|
|
270
291
|
)
|
|
271
292
|
|
|
272
293
|
# Trigger cache invalidation event
|
|
@@ -31,6 +31,7 @@ from phoenix.auth import (
|
|
|
31
31
|
DEFAULT_SYSTEM_EMAIL,
|
|
32
32
|
DEFAULT_SYSTEM_USERNAME,
|
|
33
33
|
compute_password_hash,
|
|
34
|
+
sanitize_email,
|
|
34
35
|
validate_email_format,
|
|
35
36
|
validate_password_format,
|
|
36
37
|
)
|
|
@@ -205,6 +206,8 @@ async def create_user(
|
|
|
205
206
|
) -> CreateUserResponseBody:
|
|
206
207
|
user_data = request_body.user
|
|
207
208
|
email, username, role = user_data.email, user_data.username, user_data.role
|
|
209
|
+
# Sanitize email by trimming and lowercasing
|
|
210
|
+
email = sanitize_email(email)
|
|
208
211
|
validate_email_format(email)
|
|
209
212
|
|
|
210
213
|
# Prevent creation of SYSTEM users
|
|
@@ -1,87 +1,93 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_components-
|
|
3
|
-
"file": "assets/components-
|
|
2
|
+
"_components-B1Ec5V_g.js": {
|
|
3
|
+
"file": "assets/components-B1Ec5V_g.js",
|
|
4
4
|
"name": "components",
|
|
5
5
|
"imports": [
|
|
6
|
-
"_vendor-
|
|
7
|
-
"_pages-
|
|
8
|
-
"_vendor-arizeai-
|
|
9
|
-
"_vendor-codemirror-
|
|
10
|
-
"_vendor-three-
|
|
6
|
+
"_vendor-BbqekBfb.js",
|
|
7
|
+
"_pages-B77OHHSB.js",
|
|
8
|
+
"_vendor-arizeai-CEwHhYfL.js",
|
|
9
|
+
"_vendor-codemirror-CHApHLLJ.js",
|
|
10
|
+
"_vendor-three-BLWp5bic.js"
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
|
-
"_pages-
|
|
14
|
-
"file": "assets/pages-
|
|
13
|
+
"_pages-B77OHHSB.js": {
|
|
14
|
+
"file": "assets/pages-B77OHHSB.js",
|
|
15
15
|
"name": "pages",
|
|
16
16
|
"imports": [
|
|
17
|
-
"_vendor-
|
|
18
|
-
"_vendor-arizeai-
|
|
19
|
-
"_components-
|
|
20
|
-
"_vendor-codemirror-
|
|
21
|
-
"_vendor-recharts-
|
|
17
|
+
"_vendor-BbqekBfb.js",
|
|
18
|
+
"_vendor-arizeai-CEwHhYfL.js",
|
|
19
|
+
"_components-B1Ec5V_g.js",
|
|
20
|
+
"_vendor-codemirror-CHApHLLJ.js",
|
|
21
|
+
"_vendor-recharts-Bqf7C6Cm.js"
|
|
22
22
|
]
|
|
23
23
|
},
|
|
24
|
-
"_vendor-
|
|
25
|
-
"file": "assets/vendor-
|
|
26
|
-
"src": "_vendor-CqDb5u4o.css"
|
|
27
|
-
},
|
|
28
|
-
"_vendor-_6rG8OMg.js": {
|
|
29
|
-
"file": "assets/vendor-_6rG8OMg.js",
|
|
24
|
+
"_vendor-BbqekBfb.js": {
|
|
25
|
+
"file": "assets/vendor-BbqekBfb.js",
|
|
30
26
|
"name": "vendor",
|
|
31
27
|
"imports": [
|
|
32
|
-
"_vendor-three-
|
|
28
|
+
"_vendor-three-BLWp5bic.js"
|
|
33
29
|
],
|
|
34
30
|
"css": [
|
|
35
31
|
"assets/vendor-CqDb5u4o.css"
|
|
36
32
|
]
|
|
37
33
|
},
|
|
38
|
-
"_vendor-
|
|
39
|
-
"file": "assets/vendor-
|
|
34
|
+
"_vendor-CqDb5u4o.css": {
|
|
35
|
+
"file": "assets/vendor-CqDb5u4o.css",
|
|
36
|
+
"src": "_vendor-CqDb5u4o.css"
|
|
37
|
+
},
|
|
38
|
+
"_vendor-arizeai-CEwHhYfL.js": {
|
|
39
|
+
"file": "assets/vendor-arizeai-CEwHhYfL.js",
|
|
40
40
|
"name": "vendor-arizeai",
|
|
41
41
|
"imports": [
|
|
42
|
-
"_vendor-
|
|
42
|
+
"_vendor-BbqekBfb.js"
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
|
-
"_vendor-codemirror-
|
|
46
|
-
"file": "assets/vendor-codemirror-
|
|
45
|
+
"_vendor-codemirror-CHApHLLJ.js": {
|
|
46
|
+
"file": "assets/vendor-codemirror-CHApHLLJ.js",
|
|
47
47
|
"name": "vendor-codemirror",
|
|
48
48
|
"imports": [
|
|
49
|
-
"_vendor-
|
|
50
|
-
"_vendor-shiki-
|
|
49
|
+
"_vendor-BbqekBfb.js",
|
|
50
|
+
"_vendor-shiki-BQ88Q1b1.js"
|
|
51
|
+
],
|
|
52
|
+
"dynamicImports": [
|
|
53
|
+
"_vendor-shiki-BQ88Q1b1.js",
|
|
54
|
+
"_vendor-shiki-BQ88Q1b1.js",
|
|
55
|
+
"_vendor-shiki-BQ88Q1b1.js"
|
|
51
56
|
]
|
|
52
57
|
},
|
|
53
|
-
"_vendor-recharts-
|
|
54
|
-
"file": "assets/vendor-recharts-
|
|
58
|
+
"_vendor-recharts-Bqf7C6Cm.js": {
|
|
59
|
+
"file": "assets/vendor-recharts-Bqf7C6Cm.js",
|
|
55
60
|
"name": "vendor-recharts",
|
|
56
61
|
"imports": [
|
|
57
|
-
"_vendor-
|
|
62
|
+
"_vendor-BbqekBfb.js"
|
|
58
63
|
]
|
|
59
64
|
},
|
|
60
|
-
"_vendor-shiki-
|
|
61
|
-
"file": "assets/vendor-shiki-
|
|
65
|
+
"_vendor-shiki-BQ88Q1b1.js": {
|
|
66
|
+
"file": "assets/vendor-shiki-BQ88Q1b1.js",
|
|
62
67
|
"name": "vendor-shiki",
|
|
68
|
+
"isDynamicEntry": true,
|
|
63
69
|
"imports": [
|
|
64
|
-
"_vendor-
|
|
70
|
+
"_vendor-BbqekBfb.js"
|
|
65
71
|
]
|
|
66
72
|
},
|
|
67
|
-
"_vendor-three-
|
|
68
|
-
"file": "assets/vendor-three-
|
|
73
|
+
"_vendor-three-BLWp5bic.js": {
|
|
74
|
+
"file": "assets/vendor-three-BLWp5bic.js",
|
|
69
75
|
"name": "vendor-three"
|
|
70
76
|
},
|
|
71
77
|
"index.tsx": {
|
|
72
|
-
"file": "assets/index-
|
|
78
|
+
"file": "assets/index-DYx39hbu.js",
|
|
73
79
|
"name": "index",
|
|
74
80
|
"src": "index.tsx",
|
|
75
81
|
"isEntry": true,
|
|
76
82
|
"imports": [
|
|
77
|
-
"_vendor-
|
|
78
|
-
"_vendor-arizeai-
|
|
79
|
-
"_pages-
|
|
80
|
-
"_components-
|
|
81
|
-
"_vendor-three-
|
|
82
|
-
"_vendor-codemirror-
|
|
83
|
-
"_vendor-shiki-
|
|
84
|
-
"_vendor-recharts-
|
|
83
|
+
"_vendor-BbqekBfb.js",
|
|
84
|
+
"_vendor-arizeai-CEwHhYfL.js",
|
|
85
|
+
"_pages-B77OHHSB.js",
|
|
86
|
+
"_components-B1Ec5V_g.js",
|
|
87
|
+
"_vendor-three-BLWp5bic.js",
|
|
88
|
+
"_vendor-codemirror-CHApHLLJ.js",
|
|
89
|
+
"_vendor-shiki-BQ88Q1b1.js",
|
|
90
|
+
"_vendor-recharts-Bqf7C6Cm.js"
|
|
85
91
|
]
|
|
86
92
|
}
|
|
87
93
|
}
|