arize-phoenix 8.22.1__py3-none-any.whl → 8.24.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-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/METADATA +22 -2
- {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/RECORD +31 -29
- phoenix/config.py +65 -3
- phoenix/db/facilitator.py +118 -85
- phoenix/db/helpers.py +16 -0
- phoenix/server/api/context.py +2 -0
- phoenix/server/api/mutations/user_mutations.py +10 -0
- phoenix/server/api/queries.py +3 -14
- phoenix/server/api/routers/v1/__init__.py +2 -0
- phoenix/server/api/routers/v1/projects.py +393 -0
- phoenix/server/api/subscriptions.py +1 -1
- phoenix/server/app.py +17 -0
- phoenix/server/email/sender.py +74 -47
- phoenix/server/email/templates/welcome.html +12 -0
- phoenix/server/email/types.py +16 -1
- phoenix/server/main.py +8 -1
- phoenix/server/static/.vite/manifest.json +36 -36
- phoenix/server/static/assets/{components-BAc4OPED.js → components-B6cljCxu.js} +82 -82
- phoenix/server/static/assets/{index-Du53xkjY.js → index-DfHKoAV9.js} +2 -2
- phoenix/server/static/assets/{pages-Dz-gbBPF.js → pages-Dhitcl5V.js} +342 -339
- phoenix/server/static/assets/{vendor-CEisxXSv.js → vendor-C3H3sezv.js} +1 -1
- phoenix/server/static/assets/{vendor-arizeai-BCTsSnvS.js → vendor-arizeai-DT8pwHfH.js} +1 -1
- phoenix/server/static/assets/{vendor-codemirror-DIWnRs_7.js → vendor-codemirror-DvimrGxD.js} +1 -1
- phoenix/server/static/assets/{vendor-recharts-Bame54mG.js → vendor-recharts-DuSQBcYW.js} +1 -1
- phoenix/server/static/assets/{vendor-shiki-Cc73E4D-.js → vendor-shiki-i05Hmswh.js} +1 -1
- phoenix/session/session.py +10 -0
- phoenix/version.py +1 -1
- {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/licenses/LICENSE +0 -0
phoenix/server/email/sender.py
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import smtplib
|
|
3
2
|
import ssl
|
|
4
3
|
from email.message import EmailMessage
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
from typing import Literal
|
|
7
6
|
|
|
7
|
+
from anyio import to_thread
|
|
8
8
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
|
9
|
+
from typing_extensions import TypeAlias
|
|
10
|
+
|
|
11
|
+
from phoenix.config import get_env_root_url
|
|
9
12
|
|
|
10
13
|
EMAIL_TEMPLATE_FOLDER = Path(__file__).parent / "templates"
|
|
11
14
|
|
|
15
|
+
ConnectionMethod: TypeAlias = Literal["STARTTLS", "SSL", "PLAIN"]
|
|
16
|
+
|
|
12
17
|
|
|
13
18
|
class SimpleEmailSender:
|
|
14
19
|
def __init__(
|
|
@@ -18,7 +23,7 @@ class SimpleEmailSender:
|
|
|
18
23
|
username: str,
|
|
19
24
|
password: str,
|
|
20
25
|
sender_email: str,
|
|
21
|
-
connection_method:
|
|
26
|
+
connection_method: ConnectionMethod = "STARTTLS",
|
|
22
27
|
validate_certs: bool = True,
|
|
23
28
|
) -> None:
|
|
24
29
|
self.smtp_server = smtp_server
|
|
@@ -26,7 +31,7 @@ class SimpleEmailSender:
|
|
|
26
31
|
self.username = username
|
|
27
32
|
self.password = password
|
|
28
33
|
self.sender_email = sender_email
|
|
29
|
-
self.connection_method = connection_method
|
|
34
|
+
self.connection_method: ConnectionMethod = connection_method
|
|
30
35
|
self.validate_certs = validate_certs
|
|
31
36
|
|
|
32
37
|
self.env = Environment(
|
|
@@ -34,6 +39,28 @@ class SimpleEmailSender:
|
|
|
34
39
|
autoescape=select_autoescape(["html", "xml"]),
|
|
35
40
|
)
|
|
36
41
|
|
|
42
|
+
async def send_welcome_email(
|
|
43
|
+
self,
|
|
44
|
+
email: str,
|
|
45
|
+
name: str,
|
|
46
|
+
) -> None:
|
|
47
|
+
subject = "[Phoenix] Welcome to Arize Phoenix"
|
|
48
|
+
template_name = "welcome.html"
|
|
49
|
+
|
|
50
|
+
template = self.env.get_template(template_name)
|
|
51
|
+
html_content = template.render(
|
|
52
|
+
name=name,
|
|
53
|
+
welcome_url=str(get_env_root_url()),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
msg = EmailMessage()
|
|
57
|
+
msg["Subject"] = subject
|
|
58
|
+
msg["From"] = self.sender_email
|
|
59
|
+
msg["To"] = email
|
|
60
|
+
msg.set_content(html_content, subtype="html")
|
|
61
|
+
|
|
62
|
+
await to_thread.run_sync(self._send_email, msg)
|
|
63
|
+
|
|
37
64
|
async def send_password_reset_email(
|
|
38
65
|
self,
|
|
39
66
|
email: str,
|
|
@@ -51,47 +78,47 @@ class SimpleEmailSender:
|
|
|
51
78
|
msg["To"] = email
|
|
52
79
|
msg.set_content(html_content, subtype="html")
|
|
53
80
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
server.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
81
|
+
await to_thread.run_sync(self._send_email, msg)
|
|
82
|
+
|
|
83
|
+
def _send_email(self, msg: EmailMessage) -> None:
|
|
84
|
+
context: ssl.SSLContext
|
|
85
|
+
if self.validate_certs:
|
|
86
|
+
context = ssl.create_default_context()
|
|
87
|
+
else:
|
|
88
|
+
context = ssl._create_unverified_context()
|
|
89
|
+
|
|
90
|
+
methods_to_try: list[ConnectionMethod] = [self.connection_method]
|
|
91
|
+
# add secure method fallbacks
|
|
92
|
+
if self.connection_method != "PLAIN":
|
|
93
|
+
if self.connection_method != "STARTTLS":
|
|
94
|
+
methods_to_try.append("STARTTLS")
|
|
95
|
+
if self.connection_method != "SSL":
|
|
96
|
+
methods_to_try.append("SSL")
|
|
97
|
+
|
|
98
|
+
for method in methods_to_try:
|
|
99
|
+
try:
|
|
100
|
+
if method == "STARTTLS":
|
|
101
|
+
server = smtplib.SMTP(self.smtp_server, self.smtp_port)
|
|
102
|
+
server.ehlo()
|
|
103
|
+
server.starttls(context=context)
|
|
104
|
+
server.ehlo()
|
|
105
|
+
elif method == "SSL":
|
|
106
|
+
server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port, context=context)
|
|
107
|
+
server.ehlo()
|
|
108
|
+
elif method == "PLAIN":
|
|
109
|
+
server = smtplib.SMTP(self.smtp_server, self.smtp_port)
|
|
110
|
+
server.ehlo()
|
|
111
|
+
else:
|
|
112
|
+
continue # Unsupported method
|
|
113
|
+
|
|
114
|
+
if self.username and self.password:
|
|
115
|
+
server.login(self.username, self.password)
|
|
116
|
+
|
|
117
|
+
server.send_message(msg)
|
|
118
|
+
server.quit()
|
|
119
|
+
break # Success
|
|
120
|
+
except Exception as e:
|
|
121
|
+
print(f"Failed to send email using {method}: {e}")
|
|
122
|
+
continue
|
|
123
|
+
else:
|
|
124
|
+
raise Exception("All connection methods failed")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>Welcome to Arize Phoenix</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<h1>Welcome to Arize Phoenix!</h1>
|
|
9
|
+
<p>Hi {{ name }}, please click the link below to get started:</p>
|
|
10
|
+
<a href="{{ welcome_url }}">Get Started</a>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
phoenix/server/email/types.py
CHANGED
|
@@ -3,9 +3,24 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import Protocol
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class
|
|
6
|
+
class WelcomeEmailSender(Protocol):
|
|
7
|
+
async def send_welcome_email(
|
|
8
|
+
self,
|
|
9
|
+
email: str,
|
|
10
|
+
name: str,
|
|
11
|
+
) -> None: ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PasswordResetEmailSender(Protocol):
|
|
7
15
|
async def send_password_reset_email(
|
|
8
16
|
self,
|
|
9
17
|
email: str,
|
|
10
18
|
reset_url: str,
|
|
11
19
|
) -> None: ...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EmailSender(
|
|
23
|
+
WelcomeEmailSender,
|
|
24
|
+
PasswordResetEmailSender,
|
|
25
|
+
Protocol,
|
|
26
|
+
): ...
|
phoenix/server/main.py
CHANGED
|
@@ -16,6 +16,7 @@ import phoenix.trace.v1 as pb
|
|
|
16
16
|
from phoenix.config import (
|
|
17
17
|
EXPORT_DIR,
|
|
18
18
|
get_env_access_token_expiry,
|
|
19
|
+
get_env_allowed_origins,
|
|
19
20
|
get_env_auth_settings,
|
|
20
21
|
get_env_database_connection_str,
|
|
21
22
|
get_env_database_schema,
|
|
@@ -60,6 +61,7 @@ from phoenix.server.app import (
|
|
|
60
61
|
instrument_engine_if_enabled,
|
|
61
62
|
)
|
|
62
63
|
from phoenix.server.email.sender import SimpleEmailSender
|
|
64
|
+
from phoenix.server.email.types import EmailSender
|
|
63
65
|
from phoenix.server.types import DbSessionFactory
|
|
64
66
|
from phoenix.settings import Settings
|
|
65
67
|
from phoenix.trace.fixtures import (
|
|
@@ -98,6 +100,7 @@ _WELCOME_MESSAGE = Environment(loader=BaseLoader()).from_string("""
|
|
|
98
100
|
| Phoenix UI: {{ ui_path }}
|
|
99
101
|
| Authentication: {{ auth_enabled }}
|
|
100
102
|
| Websockets: {{ websockets_enabled }}
|
|
103
|
+
{%- if allowed_origins %}\n| Allowed Origins: {{ allowed_origins }}{% endif %}
|
|
101
104
|
| Log traces:
|
|
102
105
|
| - gRPC: {{ grpc_path }}
|
|
103
106
|
| - HTTP: {{ http_path }}
|
|
@@ -359,6 +362,8 @@ def main() -> None:
|
|
|
359
362
|
if enable_websockets is None:
|
|
360
363
|
enable_websockets = True
|
|
361
364
|
|
|
365
|
+
allowed_origins = get_env_allowed_origins()
|
|
366
|
+
|
|
362
367
|
# Print information about the server
|
|
363
368
|
root_path = urljoin(f"http://{host}:{port}", host_root_path)
|
|
364
369
|
msg = _WELCOME_MESSAGE.render(
|
|
@@ -370,6 +375,7 @@ def main() -> None:
|
|
|
370
375
|
schema=get_env_database_schema(),
|
|
371
376
|
auth_enabled=authentication_enabled,
|
|
372
377
|
websockets_enabled=enable_websockets,
|
|
378
|
+
allowed_origins=allowed_origins,
|
|
373
379
|
)
|
|
374
380
|
if sys.platform.startswith("win"):
|
|
375
381
|
msg = codecs.encode(msg, "ascii", errors="ignore").decode("ascii").strip()
|
|
@@ -380,7 +386,7 @@ def main() -> None:
|
|
|
380
386
|
scaffold_datasets=scaffold_datasets,
|
|
381
387
|
phoenix_url=root_path,
|
|
382
388
|
)
|
|
383
|
-
email_sender = None
|
|
389
|
+
email_sender: Optional[EmailSender] = None
|
|
384
390
|
if mail_sever := get_env_smtp_hostname():
|
|
385
391
|
assert (mail_username := get_env_smtp_username()), "SMTP username is required"
|
|
386
392
|
assert (mail_password := get_env_smtp_password()), "SMTP password is required"
|
|
@@ -419,6 +425,7 @@ def main() -> None:
|
|
|
419
425
|
scaffolder_config=scaffolder_config,
|
|
420
426
|
email_sender=email_sender,
|
|
421
427
|
oauth2_client_configs=get_env_oauth2_settings(),
|
|
428
|
+
allowed_origins=allowed_origins,
|
|
422
429
|
)
|
|
423
430
|
server = Server(config=Config(app, host=host, port=port, root_path=host_root_path)) # type: ignore
|
|
424
431
|
Thread(target=_write_pid_file_when_ready, args=(server,), daemon=True).start()
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_components-
|
|
3
|
-
"file": "assets/components-
|
|
2
|
+
"_components-B6cljCxu.js": {
|
|
3
|
+
"file": "assets/components-B6cljCxu.js",
|
|
4
4
|
"name": "components",
|
|
5
5
|
"imports": [
|
|
6
|
-
"_vendor-
|
|
7
|
-
"_pages-
|
|
8
|
-
"_vendor-arizeai-
|
|
9
|
-
"_vendor-codemirror-
|
|
6
|
+
"_vendor-C3H3sezv.js",
|
|
7
|
+
"_pages-Dhitcl5V.js",
|
|
8
|
+
"_vendor-arizeai-DT8pwHfH.js",
|
|
9
|
+
"_vendor-codemirror-DvimrGxD.js",
|
|
10
10
|
"_vendor-three-C5WAXd5r.js"
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
|
-
"_pages-
|
|
14
|
-
"file": "assets/pages-
|
|
13
|
+
"_pages-Dhitcl5V.js": {
|
|
14
|
+
"file": "assets/pages-Dhitcl5V.js",
|
|
15
15
|
"name": "pages",
|
|
16
16
|
"imports": [
|
|
17
|
-
"_vendor-
|
|
18
|
-
"_vendor-arizeai-
|
|
19
|
-
"_components-
|
|
20
|
-
"_vendor-codemirror-
|
|
21
|
-
"_vendor-recharts-
|
|
17
|
+
"_vendor-C3H3sezv.js",
|
|
18
|
+
"_vendor-arizeai-DT8pwHfH.js",
|
|
19
|
+
"_components-B6cljCxu.js",
|
|
20
|
+
"_vendor-codemirror-DvimrGxD.js",
|
|
21
|
+
"_vendor-recharts-DuSQBcYW.js"
|
|
22
22
|
]
|
|
23
23
|
},
|
|
24
|
-
"_vendor-
|
|
25
|
-
"file": "assets/vendor-
|
|
24
|
+
"_vendor-C3H3sezv.js": {
|
|
25
|
+
"file": "assets/vendor-C3H3sezv.js",
|
|
26
26
|
"name": "vendor",
|
|
27
27
|
"imports": [
|
|
28
28
|
"_vendor-three-C5WAXd5r.js"
|
|
@@ -35,33 +35,33 @@
|
|
|
35
35
|
"file": "assets/vendor-Cg6lcjUC.css",
|
|
36
36
|
"src": "_vendor-Cg6lcjUC.css"
|
|
37
37
|
},
|
|
38
|
-
"_vendor-arizeai-
|
|
39
|
-
"file": "assets/vendor-arizeai-
|
|
38
|
+
"_vendor-arizeai-DT8pwHfH.js": {
|
|
39
|
+
"file": "assets/vendor-arizeai-DT8pwHfH.js",
|
|
40
40
|
"name": "vendor-arizeai",
|
|
41
41
|
"imports": [
|
|
42
|
-
"_vendor-
|
|
42
|
+
"_vendor-C3H3sezv.js"
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
|
-
"_vendor-codemirror-
|
|
46
|
-
"file": "assets/vendor-codemirror-
|
|
45
|
+
"_vendor-codemirror-DvimrGxD.js": {
|
|
46
|
+
"file": "assets/vendor-codemirror-DvimrGxD.js",
|
|
47
47
|
"name": "vendor-codemirror",
|
|
48
48
|
"imports": [
|
|
49
|
-
"_vendor-
|
|
50
|
-
"_vendor-shiki-
|
|
49
|
+
"_vendor-C3H3sezv.js",
|
|
50
|
+
"_vendor-shiki-i05Hmswh.js"
|
|
51
51
|
]
|
|
52
52
|
},
|
|
53
|
-
"_vendor-recharts-
|
|
54
|
-
"file": "assets/vendor-recharts-
|
|
53
|
+
"_vendor-recharts-DuSQBcYW.js": {
|
|
54
|
+
"file": "assets/vendor-recharts-DuSQBcYW.js",
|
|
55
55
|
"name": "vendor-recharts",
|
|
56
56
|
"imports": [
|
|
57
|
-
"_vendor-
|
|
57
|
+
"_vendor-C3H3sezv.js"
|
|
58
58
|
]
|
|
59
59
|
},
|
|
60
|
-
"_vendor-shiki-
|
|
61
|
-
"file": "assets/vendor-shiki-
|
|
60
|
+
"_vendor-shiki-i05Hmswh.js": {
|
|
61
|
+
"file": "assets/vendor-shiki-i05Hmswh.js",
|
|
62
62
|
"name": "vendor-shiki",
|
|
63
63
|
"imports": [
|
|
64
|
-
"_vendor-
|
|
64
|
+
"_vendor-C3H3sezv.js"
|
|
65
65
|
]
|
|
66
66
|
},
|
|
67
67
|
"_vendor-three-C5WAXd5r.js": {
|
|
@@ -69,19 +69,19 @@
|
|
|
69
69
|
"name": "vendor-three"
|
|
70
70
|
},
|
|
71
71
|
"index.tsx": {
|
|
72
|
-
"file": "assets/index-
|
|
72
|
+
"file": "assets/index-DfHKoAV9.js",
|
|
73
73
|
"name": "index",
|
|
74
74
|
"src": "index.tsx",
|
|
75
75
|
"isEntry": true,
|
|
76
76
|
"imports": [
|
|
77
|
-
"_vendor-
|
|
78
|
-
"_vendor-arizeai-
|
|
79
|
-
"_pages-
|
|
80
|
-
"_components-
|
|
77
|
+
"_vendor-C3H3sezv.js",
|
|
78
|
+
"_vendor-arizeai-DT8pwHfH.js",
|
|
79
|
+
"_pages-Dhitcl5V.js",
|
|
80
|
+
"_components-B6cljCxu.js",
|
|
81
81
|
"_vendor-three-C5WAXd5r.js",
|
|
82
|
-
"_vendor-codemirror-
|
|
83
|
-
"_vendor-shiki-
|
|
84
|
-
"_vendor-recharts-
|
|
82
|
+
"_vendor-codemirror-DvimrGxD.js",
|
|
83
|
+
"_vendor-shiki-i05Hmswh.js",
|
|
84
|
+
"_vendor-recharts-DuSQBcYW.js"
|
|
85
85
|
]
|
|
86
86
|
}
|
|
87
87
|
}
|