arize-phoenix 4.20.2__py3-none-any.whl → 4.22.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-4.20.2.dist-info → arize_phoenix-4.22.0.dist-info}/METADATA +1 -1
- {arize_phoenix-4.20.2.dist-info → arize_phoenix-4.22.0.dist-info}/RECORD +16 -13
- phoenix/config.py +59 -1
- phoenix/db/migrations/future_versions/README.md +4 -0
- phoenix/db/migrations/future_versions/cd164e83824f_users_and_tokens.py +292 -0
- phoenix/db/migrations/versions/.gitignore +1 -0
- phoenix/db/models.py +61 -0
- phoenix/server/static/.vite/manifest.json +14 -14
- phoenix/server/static/assets/{components-BSw2e1Zr.js → components-Bhx3QVW0.js} +68 -62
- phoenix/server/static/assets/{index-BYUFcdtx.js → index-CZg-95kd.js} +1 -1
- phoenix/server/static/assets/{pages-p_fuED5k.js → pages-DG-5zgoV.js} +233 -213
- phoenix/server/static/assets/{vendor-arizeai-CIETbKDq.js → vendor-arizeai-Sj74jm5V.js} +1 -1
- phoenix/version.py +1 -1
- {arize_phoenix-4.20.2.dist-info → arize_phoenix-4.22.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-4.20.2.dist-info → arize_phoenix-4.22.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-4.20.2.dist-info → arize_phoenix-4.22.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
phoenix/__init__.py,sha256=TGNWqm2UW-l67yIRpOtmqGHVAmdoobSNqUsiTtip7uQ,1542
|
|
2
|
-
phoenix/config.py,sha256=
|
|
2
|
+
phoenix/config.py,sha256=wYA_8GSSz5rnpfIWDjeBL9ehKuTy9jqXaMZnxUqRYEU,10131
|
|
3
3
|
phoenix/datetime_utils.py,sha256=yDKjwX2Vtqw9h5F_ProtP-TsXidM43uIvmJ_pOzYc9A,3405
|
|
4
4
|
phoenix/exceptions.py,sha256=n2L2KKuecrdflB9MsCdAYCiSEvGJptIsfRkXMoJle7A,169
|
|
5
5
|
phoenix/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
6
6
|
phoenix/services.py,sha256=aTxhcOA1pZHB6U-B3TEcp6fqDF5oT0xCUvEUNMZVTUQ,5175
|
|
7
7
|
phoenix/settings.py,sha256=cO-qgis_S27nHirTobYI9hHPfZH18R--WMmxNdsVUwc,273
|
|
8
|
-
phoenix/version.py,sha256=
|
|
8
|
+
phoenix/version.py,sha256=QzEW4rw3knzx0cHKAbjoAdUuGoTl82VIDrnWP2UhHw0,23
|
|
9
9
|
phoenix/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
phoenix/core/embedding_dimension.py,sha256=zKGbcvwOXgLf-yrJBpQyKtd-LEOPRKHnUToyAU8Owis,87
|
|
11
11
|
phoenix/core/model.py,sha256=km_a--PBHOuA337ClRw9xqhOHhrUT6Rl9pz_zV0JYkQ,4843
|
|
@@ -18,7 +18,7 @@ phoenix/db/bulk_inserter.py,sha256=qgg8pt5k4VnHKOE0-KoReXVAfXRhLt-sMZihI-b4X9I,1
|
|
|
18
18
|
phoenix/db/engines.py,sha256=R3btYTSOSd6BwRA59EmhhojL0HCQ7NnzFIXQrPYS0iU,4812
|
|
19
19
|
phoenix/db/helpers.py,sha256=2zSc4n5IJfu-CaOFoBfqTB35M1nTFcAc8tqLsNtF2Jw,3488
|
|
20
20
|
phoenix/db/migrate.py,sha256=MuhtNWnR24riROvarvKfbRb4_D5xuQi6P760vBUKl1E,2270
|
|
21
|
-
phoenix/db/models.py,sha256=
|
|
21
|
+
phoenix/db/models.py,sha256=C_s1TAgMRi2eExc48NwInErsfSzVhNZtj4ow23jUKSc,23352
|
|
22
22
|
phoenix/db/insertion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
phoenix/db/insertion/constants.py,sha256=8wifm7X-1XvroZ__R2Gc96NsgLhTDn0zXl4lehlXtcA,70
|
|
24
24
|
phoenix/db/insertion/dataset.py,sha256=_vxy5e6W5jEuvO2fMKbbNCn9JvHkwI4LRKk_10eKFVg,7171
|
|
@@ -32,6 +32,9 @@ phoenix/db/insertion/types.py,sha256=nQYYnpzcPxj2kdUoXfKE8ilOKlx1zpKLPc40OGuBlfk
|
|
|
32
32
|
phoenix/db/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
phoenix/db/migrations/env.py,sha256=QbzB5zrRs6XQQmrYeUpuzeilcMlM-MsbaAgHHYcIHTI,3626
|
|
34
34
|
phoenix/db/migrations/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
|
|
35
|
+
phoenix/db/migrations/future_versions/README.md,sha256=3QtDx40SAD-IITjbdlKR2N_CBxT5y37C1OQs05EDt7o,184
|
|
36
|
+
phoenix/db/migrations/future_versions/cd164e83824f_users_and_tokens.py,sha256=L3UlrrT9_mgIakkjRl4MfSHd5dNmZi0D0jizi5V4dTI,8795
|
|
37
|
+
phoenix/db/migrations/versions/.gitignore,sha256=chLdMrfkICZvLY7lCEcuqF32sVp61Jml4PodFryEU94,33
|
|
35
38
|
phoenix/db/migrations/versions/10460e46d750_datasets.py,sha256=eZAyz720DmpOd7RnuxDN2dVNXVuMrdlCA7eAOxyMtfs,8695
|
|
36
39
|
phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py,sha256=x6oKFwn7Zmite4G0trDQPpMCn0I7jejuBcN3-ivEuDg,3938
|
|
37
40
|
phoenix/db/migrations/versions/cf03bd6bae1d_init.py,sha256=09cpofqje8zi4eQFfUn-i21x7VcsUYOfLKKUlrtKrGc,8662
|
|
@@ -227,13 +230,13 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
|
|
|
227
230
|
phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
|
|
228
231
|
phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
|
|
229
232
|
phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
|
|
230
|
-
phoenix/server/static/.vite/manifest.json,sha256=
|
|
231
|
-
phoenix/server/static/assets/components-
|
|
232
|
-
phoenix/server/static/assets/index-
|
|
233
|
-
phoenix/server/static/assets/pages-
|
|
233
|
+
phoenix/server/static/.vite/manifest.json,sha256=HOOexYONSka_hn_J9xhFRtKsvGXqttAcXqmC1C9uSLI,1929
|
|
234
|
+
phoenix/server/static/assets/components-Bhx3QVW0.js,sha256=5UWD5GSontt9-H7IMs7lRbds6I4RyCbeRwFJRM52DGw,187118
|
|
235
|
+
phoenix/server/static/assets/index-CZg-95kd.js,sha256=NOPlnhvT31fNg9srw7-kvKfoBLFqVbwlTvUrYKYIYeQ,7362
|
|
236
|
+
phoenix/server/static/assets/pages-DG-5zgoV.js,sha256=m2FGLIvrM7I3uweAC_5YcXZfmdTI3mVeVCgLCcB23B0,452737
|
|
234
237
|
phoenix/server/static/assets/vendor-BMWfu6zp.js,sha256=AAVTM5SjGUI_CmAWFUFmhpp5VDhvCD-MrEoh-pXXADY,1355423
|
|
235
238
|
phoenix/server/static/assets/vendor-DxkFTwjz.css,sha256=nZrkr0u6NNElFGvpWHk9GTHeGoibCXCli1bE7mXZGZg,1816
|
|
236
|
-
phoenix/server/static/assets/vendor-arizeai-
|
|
239
|
+
phoenix/server/static/assets/vendor-arizeai-Sj74jm5V.js,sha256=9lD4YeMt5WtyfrqIApcH9WFQxyJJUtth0syWabkzX-I,304008
|
|
237
240
|
phoenix/server/static/assets/vendor-codemirror-DO3VqEcD.js,sha256=M7t6xd6WpgKes25OOeGyxT1MU1dDrEKdmUBHgy5zslw,503031
|
|
238
241
|
phoenix/server/static/assets/vendor-recharts-BGN0SxgJ.js,sha256=L9LAYSjuf0GHh1_PQh9bF4l9euWCDVQcnQN1RgMDMBw,282859
|
|
239
242
|
phoenix/server/static/assets/vendor-three-DwGkEfCM.js,sha256=0D12ZgKzfKCTSdSTKJBFR2RZO_xxeMXrqDp0AszZqHY,620972
|
|
@@ -281,8 +284,8 @@ phoenix/utilities/logging.py,sha256=lDXd6EGaamBNcQxL4vP1au9-i_SXe0OraUDiJOcszSw,
|
|
|
281
284
|
phoenix/utilities/project.py,sha256=8IJuMM4yUMoooPi37sictGj8Etu9rGmq6RFtc9848cQ,436
|
|
282
285
|
phoenix/utilities/re.py,sha256=PDve_OLjRTM8yQQJHC8-n3HdIONi7aNils3ZKRZ5uBM,2045
|
|
283
286
|
phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
284
|
-
arize_phoenix-4.
|
|
285
|
-
arize_phoenix-4.
|
|
286
|
-
arize_phoenix-4.
|
|
287
|
-
arize_phoenix-4.
|
|
288
|
-
arize_phoenix-4.
|
|
287
|
+
arize_phoenix-4.22.0.dist-info/METADATA,sha256=knv747RowfHFOmv5WqkavBVG6KBadXQ8kLol_iYJK-Q,11902
|
|
288
|
+
arize_phoenix-4.22.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
289
|
+
arize_phoenix-4.22.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
|
|
290
|
+
arize_phoenix-4.22.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
|
|
291
|
+
arize_phoenix-4.22.0.dist-info/RECORD,,
|
phoenix/config.py
CHANGED
|
@@ -2,7 +2,7 @@ import os
|
|
|
2
2
|
import tempfile
|
|
3
3
|
from logging import getLogger
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Dict, List, Optional
|
|
5
|
+
from typing import Dict, List, Optional, Tuple
|
|
6
6
|
|
|
7
7
|
from .utilities.re import parse_env_headers
|
|
8
8
|
|
|
@@ -60,6 +60,11 @@ ENV_PHOENIX_SERVER_INSTRUMENTATION_OTLP_TRACE_COLLECTOR_GRPC_ENDPOINT = (
|
|
|
60
60
|
"PHOENIX_SERVER_INSTRUMENTATION_OTLP_TRACE_COLLECTOR_GRPC_ENDPOINT"
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
+
# Auth is under active development. Phoenix users are strongly advised not to
|
|
64
|
+
# set these environment variables until the feature is officially released.
|
|
65
|
+
ENV_DANGEROUSLY_SET_PHOENIX_ENABLE_AUTH = "DANGEROUSLY_SET_PHOENIX_ENABLE_AUTH"
|
|
66
|
+
ENV_DANGEROUSLY_SET_PHOENIX_SECRET = "DANGEROUSLY_SET_PHOENIX_SECRET"
|
|
67
|
+
|
|
63
68
|
|
|
64
69
|
def server_instrumentation_is_enabled() -> bool:
|
|
65
70
|
return bool(
|
|
@@ -100,6 +105,57 @@ def get_working_dir() -> Path:
|
|
|
100
105
|
return Path.home().resolve() / ".phoenix"
|
|
101
106
|
|
|
102
107
|
|
|
108
|
+
def get_boolean_env_var(env_var: str) -> Optional[bool]:
|
|
109
|
+
"""
|
|
110
|
+
Parses a boolean environment variable, returning None if the variable is not set.
|
|
111
|
+
"""
|
|
112
|
+
if (value := os.environ.get(env_var)) is None:
|
|
113
|
+
return None
|
|
114
|
+
assert (lower := value.lower()) in (
|
|
115
|
+
"true",
|
|
116
|
+
"false",
|
|
117
|
+
), f"{env_var} must be set to TRUE or FALSE (case-insensitive)"
|
|
118
|
+
return lower == "true"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_env_enable_auth() -> bool:
|
|
122
|
+
"""
|
|
123
|
+
Gets the value of the DANGEROUSLY_SET_PHOENIX_ENABLE_AUTH environment variable.
|
|
124
|
+
"""
|
|
125
|
+
return get_boolean_env_var(ENV_DANGEROUSLY_SET_PHOENIX_ENABLE_AUTH) is True
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_env_phoenix_secret() -> Optional[str]:
|
|
129
|
+
"""
|
|
130
|
+
Gets the value of the DANGEROUSLY_SET_PHOENIX_SECRET environment variable
|
|
131
|
+
and performs validation.
|
|
132
|
+
"""
|
|
133
|
+
phoenix_secret = os.environ.get(ENV_DANGEROUSLY_SET_PHOENIX_SECRET)
|
|
134
|
+
if phoenix_secret is None:
|
|
135
|
+
return None
|
|
136
|
+
# todo: add validation for the phoenix secret
|
|
137
|
+
return phoenix_secret
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_auth_settings() -> Tuple[bool, Optional[str]]:
|
|
141
|
+
"""
|
|
142
|
+
Gets auth settings and performs validation.
|
|
143
|
+
"""
|
|
144
|
+
enable_auth = get_env_enable_auth()
|
|
145
|
+
phoenix_secret = get_env_phoenix_secret()
|
|
146
|
+
if enable_auth:
|
|
147
|
+
assert phoenix_secret, (
|
|
148
|
+
"DANGEROUSLY_SET_PHOENIX_SECRET must be set "
|
|
149
|
+
"when auth is enabled with DANGEROUSLY_SET_PHOENIX_ENABLE_AUTH"
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
assert not phoenix_secret, (
|
|
153
|
+
"DANGEROUSLY_SET_PHOENIX_SECRET cannot be set "
|
|
154
|
+
"unless auth is enabled with DANGEROUSLY_SET_PHOENIX_ENABLE_AUTH"
|
|
155
|
+
)
|
|
156
|
+
return enable_auth, phoenix_secret
|
|
157
|
+
|
|
158
|
+
|
|
103
159
|
PHOENIX_DIR = Path(__file__).resolve().parent
|
|
104
160
|
# Server config
|
|
105
161
|
SERVER_DIR = PHOENIX_DIR / "server"
|
|
@@ -123,6 +179,8 @@ EXPORT_DIR = ROOT_DIR / "exports"
|
|
|
123
179
|
INFERENCES_DIR = ROOT_DIR / "inferences"
|
|
124
180
|
TRACE_DATASETS_DIR = ROOT_DIR / "trace_datasets"
|
|
125
181
|
|
|
182
|
+
ENABLE_AUTH, PHOENIX_SECRET = get_auth_settings()
|
|
183
|
+
|
|
126
184
|
|
|
127
185
|
def ensure_working_dir() -> None:
|
|
128
186
|
"""
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""users and tokens
|
|
2
|
+
|
|
3
|
+
Revision ID: cd164e83824f
|
|
4
|
+
Revises: 10460e46d750
|
|
5
|
+
Create Date: 2024-08-01 18:36:52.157604
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Any, Dict, List, Optional, Sequence, TypedDict, Union
|
|
11
|
+
|
|
12
|
+
import sqlalchemy as sa
|
|
13
|
+
from alembic import op
|
|
14
|
+
from phoenix.datetime_utils import normalize_datetime
|
|
15
|
+
from sqlalchemy import (
|
|
16
|
+
JSON,
|
|
17
|
+
TIMESTAMP,
|
|
18
|
+
CheckConstraint,
|
|
19
|
+
Dialect,
|
|
20
|
+
ForeignKey,
|
|
21
|
+
MetaData,
|
|
22
|
+
TypeDecorator,
|
|
23
|
+
func,
|
|
24
|
+
insert,
|
|
25
|
+
)
|
|
26
|
+
from sqlalchemy.dialects import postgresql
|
|
27
|
+
from sqlalchemy.ext.asyncio.engine import AsyncConnection
|
|
28
|
+
from sqlalchemy.ext.compiler import compiles
|
|
29
|
+
from sqlalchemy.orm import (
|
|
30
|
+
DeclarativeBase,
|
|
31
|
+
Mapped,
|
|
32
|
+
mapped_column,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class JSONB(JSON):
|
|
37
|
+
# See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
38
|
+
__visit_name__ = "JSONB"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@compiles(JSONB, "sqlite") # type: ignore
|
|
42
|
+
def _(*args: Any, **kwargs: Any) -> str:
|
|
43
|
+
# See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
44
|
+
return "JSONB"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
JSON_ = (
|
|
48
|
+
JSON()
|
|
49
|
+
.with_variant(
|
|
50
|
+
postgresql.JSONB(), # type: ignore
|
|
51
|
+
"postgresql",
|
|
52
|
+
)
|
|
53
|
+
.with_variant(
|
|
54
|
+
JSONB(),
|
|
55
|
+
"sqlite",
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class JsonDict(TypeDecorator[Dict[str, Any]]):
|
|
61
|
+
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
62
|
+
cache_ok = True
|
|
63
|
+
impl = JSON_
|
|
64
|
+
|
|
65
|
+
def process_bind_param(self, value: Optional[Dict[str, Any]], _: Dialect) -> Dict[str, Any]:
|
|
66
|
+
return value if isinstance(value, dict) else {}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class JsonList(TypeDecorator[List[Any]]):
|
|
70
|
+
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
71
|
+
cache_ok = True
|
|
72
|
+
impl = JSON_
|
|
73
|
+
|
|
74
|
+
def process_bind_param(self, value: Optional[List[Any]], _: Dialect) -> List[Any]:
|
|
75
|
+
return value if isinstance(value, list) else []
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class UtcTimeStamp(TypeDecorator[datetime]):
|
|
79
|
+
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
80
|
+
cache_ok = True
|
|
81
|
+
impl = TIMESTAMP(timezone=True)
|
|
82
|
+
|
|
83
|
+
def process_bind_param(self, value: Optional[datetime], _: Dialect) -> Optional[datetime]:
|
|
84
|
+
return normalize_datetime(value)
|
|
85
|
+
|
|
86
|
+
def process_result_value(self, value: Optional[Any], _: Dialect) -> Optional[datetime]:
|
|
87
|
+
return normalize_datetime(value, timezone.utc)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ExperimentRunOutput(TypedDict, total=False):
|
|
91
|
+
task_output: Any
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Base(DeclarativeBase):
|
|
95
|
+
# Enforce best practices for naming constraints
|
|
96
|
+
# https://alembic.sqlalchemy.org/en/latest/naming.html#integration-of-naming-conventions-into-operations-autogenerate
|
|
97
|
+
metadata = MetaData(
|
|
98
|
+
naming_convention={
|
|
99
|
+
"ix": "ix_%(table_name)s_%(column_0_N_name)s",
|
|
100
|
+
"uq": "uq_%(table_name)s_%(column_0_N_name)s",
|
|
101
|
+
"ck": "ck_%(table_name)s_`%(constraint_name)s`",
|
|
102
|
+
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
|
103
|
+
"pk": "pk_%(table_name)s",
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
type_annotation_map = {
|
|
107
|
+
Dict[str, Any]: JsonDict,
|
|
108
|
+
List[Dict[str, Any]]: JsonList,
|
|
109
|
+
ExperimentRunOutput: JsonDict,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class UserRole(Base):
|
|
114
|
+
__tablename__ = "user_roles"
|
|
115
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
116
|
+
role: Mapped[str] = mapped_column(unique=True)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class User(Base):
|
|
120
|
+
__tablename__ = "users"
|
|
121
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
122
|
+
user_role_id: Mapped[int] = mapped_column(
|
|
123
|
+
ForeignKey("user_roles.id"),
|
|
124
|
+
index=True,
|
|
125
|
+
)
|
|
126
|
+
username: Mapped[Optional[str]] = mapped_column(nullable=True, unique=True, index=True)
|
|
127
|
+
email: Mapped[str] = mapped_column(nullable=False, unique=True, index=True)
|
|
128
|
+
auth_method: Mapped[str] = mapped_column(
|
|
129
|
+
CheckConstraint("auth_method IN ('LOCAL')", name="valid_auth_method")
|
|
130
|
+
)
|
|
131
|
+
password_hash: Mapped[Optional[str]]
|
|
132
|
+
reset_password: Mapped[bool]
|
|
133
|
+
created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
|
|
134
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
135
|
+
UtcTimeStamp, server_default=func.now(), onupdate=func.now()
|
|
136
|
+
)
|
|
137
|
+
deleted_at: Mapped[Optional[datetime]] = mapped_column(UtcTimeStamp)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# revision identifiers, used by Alembic.
|
|
141
|
+
revision: str = "cd164e83824f"
|
|
142
|
+
down_revision: Union[str, None] = "3be8647b87d8"
|
|
143
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
144
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
async def insert_roles_and_users(connection: AsyncConnection) -> None:
|
|
148
|
+
"""
|
|
149
|
+
Populates the `user_roles` table and adds a system user and initial admin
|
|
150
|
+
user to the `users` table.
|
|
151
|
+
"""
|
|
152
|
+
await connection.execute(
|
|
153
|
+
insert(UserRole).values([{"role": "SYSTEM"}, {"role": "ADMIN"}, {"role": "GENERAL"}])
|
|
154
|
+
)
|
|
155
|
+
system_user_role_id = sa.select(UserRole.id).where(UserRole.role == "SYSTEM").scalar_subquery()
|
|
156
|
+
admin_user_role_id = sa.select(UserRole.id).where(UserRole.role == "ADMIN").scalar_subquery()
|
|
157
|
+
await connection.execute(
|
|
158
|
+
insert(User).values(
|
|
159
|
+
[
|
|
160
|
+
{
|
|
161
|
+
"user_role_id": system_user_role_id,
|
|
162
|
+
"username": None,
|
|
163
|
+
"email": "system@localhost",
|
|
164
|
+
"auth_method": "LOCAL",
|
|
165
|
+
"password_hash": None,
|
|
166
|
+
"reset_password": False,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"user_role_id": admin_user_role_id,
|
|
170
|
+
"username": "admin",
|
|
171
|
+
"email": "admin@localhost",
|
|
172
|
+
"auth_method": "LOCAL",
|
|
173
|
+
"password_hash": None, # todo: replace this with the hashed PHOENIX_SECRET
|
|
174
|
+
"reset_password": True,
|
|
175
|
+
},
|
|
176
|
+
]
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def upgrade() -> None:
|
|
182
|
+
op.create_table(
|
|
183
|
+
"user_roles",
|
|
184
|
+
sa.Column("id", sa.Integer, primary_key=True),
|
|
185
|
+
sa.Column(
|
|
186
|
+
"role",
|
|
187
|
+
sa.String,
|
|
188
|
+
nullable=False,
|
|
189
|
+
unique=True,
|
|
190
|
+
),
|
|
191
|
+
)
|
|
192
|
+
op.create_table(
|
|
193
|
+
"users",
|
|
194
|
+
sa.Column("id", sa.Integer, primary_key=True),
|
|
195
|
+
sa.Column(
|
|
196
|
+
"user_role_id",
|
|
197
|
+
sa.Integer,
|
|
198
|
+
sa.ForeignKey("user_roles.id"),
|
|
199
|
+
nullable=False,
|
|
200
|
+
index=True,
|
|
201
|
+
),
|
|
202
|
+
sa.Column("username", sa.String, nullable=True, unique=True, index=True),
|
|
203
|
+
sa.Column("email", sa.String, nullable=False, unique=True, index=True),
|
|
204
|
+
sa.Column(
|
|
205
|
+
"auth_method",
|
|
206
|
+
sa.String,
|
|
207
|
+
sa.CheckConstraint("auth_method IN ('LOCAL')", "valid_auth_method"),
|
|
208
|
+
nullable=False,
|
|
209
|
+
),
|
|
210
|
+
sa.Column("password_hash", sa.String, nullable=True),
|
|
211
|
+
sa.Column("reset_password", sa.Boolean, nullable=False),
|
|
212
|
+
sa.Column(
|
|
213
|
+
"created_at",
|
|
214
|
+
sa.TIMESTAMP(timezone=True),
|
|
215
|
+
nullable=False,
|
|
216
|
+
server_default=sa.func.now(),
|
|
217
|
+
),
|
|
218
|
+
sa.Column(
|
|
219
|
+
"updated_at",
|
|
220
|
+
sa.TIMESTAMP(timezone=True),
|
|
221
|
+
nullable=False,
|
|
222
|
+
server_default=sa.func.now(),
|
|
223
|
+
onupdate=sa.func.now(),
|
|
224
|
+
),
|
|
225
|
+
sa.Column(
|
|
226
|
+
"deleted_at",
|
|
227
|
+
sa.TIMESTAMP(timezone=True),
|
|
228
|
+
nullable=True,
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
op.create_table(
|
|
232
|
+
"api_keys",
|
|
233
|
+
sa.Column("id", sa.Integer, primary_key=True),
|
|
234
|
+
sa.Column(
|
|
235
|
+
"user_id",
|
|
236
|
+
sa.Integer,
|
|
237
|
+
sa.ForeignKey("users.id"),
|
|
238
|
+
nullable=False,
|
|
239
|
+
index=True,
|
|
240
|
+
),
|
|
241
|
+
sa.Column("name", sa.String, nullable=False),
|
|
242
|
+
sa.Column("description", sa.String, nullable=True),
|
|
243
|
+
sa.Column(
|
|
244
|
+
"created_at",
|
|
245
|
+
sa.TIMESTAMP(timezone=True),
|
|
246
|
+
nullable=False,
|
|
247
|
+
server_default=sa.func.now(),
|
|
248
|
+
),
|
|
249
|
+
sa.Column(
|
|
250
|
+
"expires_at",
|
|
251
|
+
sa.TIMESTAMP(timezone=True),
|
|
252
|
+
nullable=True,
|
|
253
|
+
),
|
|
254
|
+
)
|
|
255
|
+
op.create_table(
|
|
256
|
+
"audit_api_keys",
|
|
257
|
+
sa.Column("id", sa.Integer, primary_key=True),
|
|
258
|
+
sa.Column(
|
|
259
|
+
"api_key_id",
|
|
260
|
+
sa.Integer,
|
|
261
|
+
sa.ForeignKey("api_keys.id"),
|
|
262
|
+
nullable=False,
|
|
263
|
+
index=True,
|
|
264
|
+
),
|
|
265
|
+
sa.Column(
|
|
266
|
+
"user_id",
|
|
267
|
+
sa.Integer,
|
|
268
|
+
sa.ForeignKey("users.id"),
|
|
269
|
+
nullable=False,
|
|
270
|
+
index=True,
|
|
271
|
+
),
|
|
272
|
+
sa.Column(
|
|
273
|
+
"action",
|
|
274
|
+
sa.String,
|
|
275
|
+
sa.CheckConstraint("action IN ('CREATE', 'DELETE')", "valid_action"),
|
|
276
|
+
nullable=False,
|
|
277
|
+
),
|
|
278
|
+
sa.Column(
|
|
279
|
+
"created_at",
|
|
280
|
+
sa.TIMESTAMP(timezone=True),
|
|
281
|
+
nullable=False,
|
|
282
|
+
server_default=sa.func.now(),
|
|
283
|
+
),
|
|
284
|
+
)
|
|
285
|
+
op.run_async(insert_roles_and_users)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def downgrade() -> None:
|
|
289
|
+
op.drop_table("audit_api_keys")
|
|
290
|
+
op.drop_table("api_keys")
|
|
291
|
+
op.drop_table("users")
|
|
292
|
+
op.drop_table("user_roles")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cd164e83824f_users_and_tokens.py
|
phoenix/db/models.py
CHANGED
|
@@ -34,6 +34,7 @@ from sqlalchemy.orm import (
|
|
|
34
34
|
)
|
|
35
35
|
from sqlalchemy.sql import expression
|
|
36
36
|
|
|
37
|
+
from phoenix.config import ENABLE_AUTH
|
|
37
38
|
from phoenix.datetime_utils import normalize_datetime
|
|
38
39
|
|
|
39
40
|
|
|
@@ -616,3 +617,63 @@ class ExperimentRunAnnotation(Base):
|
|
|
616
617
|
"name",
|
|
617
618
|
),
|
|
618
619
|
)
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
# todo: unnest the following models when auth is released (https://github.com/Arize-ai/phoenix/issues/4183)
|
|
623
|
+
if ENABLE_AUTH:
|
|
624
|
+
|
|
625
|
+
class UserRole(Base):
|
|
626
|
+
__tablename__ = "user_roles"
|
|
627
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
628
|
+
role: Mapped[str] = mapped_column(unique=True)
|
|
629
|
+
|
|
630
|
+
class User(Base):
|
|
631
|
+
__tablename__ = "users"
|
|
632
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
633
|
+
user_role_id: Mapped[int] = mapped_column(
|
|
634
|
+
ForeignKey("user_roles.id"),
|
|
635
|
+
index=True,
|
|
636
|
+
)
|
|
637
|
+
username: Mapped[Optional[str]] = mapped_column(nullable=True, unique=True, index=True)
|
|
638
|
+
email: Mapped[str] = mapped_column(nullable=False, unique=True, index=True)
|
|
639
|
+
auth_method: Mapped[str] = mapped_column(
|
|
640
|
+
CheckConstraint("auth_method IN ('LOCAL')", name="valid_auth_method")
|
|
641
|
+
)
|
|
642
|
+
password_hash: Mapped[Optional[str]]
|
|
643
|
+
reset_password: Mapped[bool]
|
|
644
|
+
created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
|
|
645
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
646
|
+
UtcTimeStamp, server_default=func.now(), onupdate=func.now()
|
|
647
|
+
)
|
|
648
|
+
deleted_at: Mapped[Optional[datetime]] = mapped_column(UtcTimeStamp)
|
|
649
|
+
|
|
650
|
+
class APIKey(Base):
|
|
651
|
+
__tablename__ = "api_keys"
|
|
652
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
653
|
+
user_id: Mapped[int] = mapped_column(
|
|
654
|
+
ForeignKey("users.id"),
|
|
655
|
+
index=True,
|
|
656
|
+
)
|
|
657
|
+
name: Mapped[str]
|
|
658
|
+
description: Mapped[Optional[str]]
|
|
659
|
+
created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
|
|
660
|
+
expires_at: Mapped[Optional[datetime]] = mapped_column(UtcTimeStamp)
|
|
661
|
+
|
|
662
|
+
# todo: standardize audit table format (https://github.com/Arize-ai/phoenix/issues/4185)
|
|
663
|
+
class AuditAPIKey(Base):
|
|
664
|
+
__tablename__ = "audit_api_keys"
|
|
665
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
666
|
+
api_key_id: Mapped[int] = mapped_column(
|
|
667
|
+
ForeignKey("api_keys.id"),
|
|
668
|
+
nullable=False,
|
|
669
|
+
index=True,
|
|
670
|
+
)
|
|
671
|
+
user_id: Mapped[int] = mapped_column(
|
|
672
|
+
ForeignKey("users.id"),
|
|
673
|
+
nullable=False,
|
|
674
|
+
index=True,
|
|
675
|
+
)
|
|
676
|
+
action: Mapped[str] = mapped_column(
|
|
677
|
+
CheckConstraint("action IN ('CREATE', 'DELETE')", name="valid_action")
|
|
678
|
+
)
|
|
679
|
+
created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_components-
|
|
3
|
-
"file": "assets/components-
|
|
2
|
+
"_components-Bhx3QVW0.js": {
|
|
3
|
+
"file": "assets/components-Bhx3QVW0.js",
|
|
4
4
|
"name": "components",
|
|
5
5
|
"imports": [
|
|
6
6
|
"_vendor-BMWfu6zp.js",
|
|
7
|
-
"_vendor-arizeai-
|
|
8
|
-
"_pages-
|
|
7
|
+
"_vendor-arizeai-Sj74jm5V.js",
|
|
8
|
+
"_pages-DG-5zgoV.js",
|
|
9
9
|
"_vendor-three-DwGkEfCM.js",
|
|
10
10
|
"_vendor-codemirror-DO3VqEcD.js"
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
|
-
"_pages-
|
|
14
|
-
"file": "assets/pages-
|
|
13
|
+
"_pages-DG-5zgoV.js": {
|
|
14
|
+
"file": "assets/pages-DG-5zgoV.js",
|
|
15
15
|
"name": "pages",
|
|
16
16
|
"imports": [
|
|
17
17
|
"_vendor-BMWfu6zp.js",
|
|
18
|
-
"_components-
|
|
19
|
-
"_vendor-arizeai-
|
|
18
|
+
"_components-Bhx3QVW0.js",
|
|
19
|
+
"_vendor-arizeai-Sj74jm5V.js",
|
|
20
20
|
"_vendor-recharts-BGN0SxgJ.js",
|
|
21
21
|
"_vendor-codemirror-DO3VqEcD.js"
|
|
22
22
|
]
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"assets/vendor-DxkFTwjz.css"
|
|
36
36
|
]
|
|
37
37
|
},
|
|
38
|
-
"_vendor-arizeai-
|
|
39
|
-
"file": "assets/vendor-arizeai-
|
|
38
|
+
"_vendor-arizeai-Sj74jm5V.js": {
|
|
39
|
+
"file": "assets/vendor-arizeai-Sj74jm5V.js",
|
|
40
40
|
"name": "vendor-arizeai",
|
|
41
41
|
"imports": [
|
|
42
42
|
"_vendor-BMWfu6zp.js"
|
|
@@ -61,15 +61,15 @@
|
|
|
61
61
|
"name": "vendor-three"
|
|
62
62
|
},
|
|
63
63
|
"index.tsx": {
|
|
64
|
-
"file": "assets/index-
|
|
64
|
+
"file": "assets/index-CZg-95kd.js",
|
|
65
65
|
"name": "index",
|
|
66
66
|
"src": "index.tsx",
|
|
67
67
|
"isEntry": true,
|
|
68
68
|
"imports": [
|
|
69
69
|
"_vendor-BMWfu6zp.js",
|
|
70
|
-
"_vendor-arizeai-
|
|
71
|
-
"_components-
|
|
72
|
-
"_pages-
|
|
70
|
+
"_vendor-arizeai-Sj74jm5V.js",
|
|
71
|
+
"_components-Bhx3QVW0.js",
|
|
72
|
+
"_pages-DG-5zgoV.js",
|
|
73
73
|
"_vendor-three-DwGkEfCM.js",
|
|
74
74
|
"_vendor-codemirror-DO3VqEcD.js",
|
|
75
75
|
"_vendor-recharts-BGN0SxgJ.js"
|