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.

Files changed (31) hide show
  1. {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/METADATA +22 -2
  2. {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/RECORD +31 -29
  3. phoenix/config.py +65 -3
  4. phoenix/db/facilitator.py +118 -85
  5. phoenix/db/helpers.py +16 -0
  6. phoenix/server/api/context.py +2 -0
  7. phoenix/server/api/mutations/user_mutations.py +10 -0
  8. phoenix/server/api/queries.py +3 -14
  9. phoenix/server/api/routers/v1/__init__.py +2 -0
  10. phoenix/server/api/routers/v1/projects.py +393 -0
  11. phoenix/server/api/subscriptions.py +1 -1
  12. phoenix/server/app.py +17 -0
  13. phoenix/server/email/sender.py +74 -47
  14. phoenix/server/email/templates/welcome.html +12 -0
  15. phoenix/server/email/types.py +16 -1
  16. phoenix/server/main.py +8 -1
  17. phoenix/server/static/.vite/manifest.json +36 -36
  18. phoenix/server/static/assets/{components-BAc4OPED.js → components-B6cljCxu.js} +82 -82
  19. phoenix/server/static/assets/{index-Du53xkjY.js → index-DfHKoAV9.js} +2 -2
  20. phoenix/server/static/assets/{pages-Dz-gbBPF.js → pages-Dhitcl5V.js} +342 -339
  21. phoenix/server/static/assets/{vendor-CEisxXSv.js → vendor-C3H3sezv.js} +1 -1
  22. phoenix/server/static/assets/{vendor-arizeai-BCTsSnvS.js → vendor-arizeai-DT8pwHfH.js} +1 -1
  23. phoenix/server/static/assets/{vendor-codemirror-DIWnRs_7.js → vendor-codemirror-DvimrGxD.js} +1 -1
  24. phoenix/server/static/assets/{vendor-recharts-Bame54mG.js → vendor-recharts-DuSQBcYW.js} +1 -1
  25. phoenix/server/static/assets/{vendor-shiki-Cc73E4D-.js → vendor-shiki-i05Hmswh.js} +1 -1
  26. phoenix/session/session.py +10 -0
  27. phoenix/version.py +1 -1
  28. {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/WHEEL +0 -0
  29. {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/entry_points.txt +0 -0
  30. {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/licenses/IP_NOTICE +0 -0
  31. {arize_phoenix-8.22.1.dist-info → arize_phoenix-8.24.0.dist-info}/licenses/LICENSE +0 -0
@@ -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: Literal["STARTTLS", "SSL", "PLAIN"] = "STARTTLS",
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.upper()
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
- def send_email() -> None:
55
- context: ssl.SSLContext
56
- if self.validate_certs:
57
- context = ssl.create_default_context()
58
- else:
59
- context = ssl._create_unverified_context()
60
-
61
- methods_to_try = [self.connection_method]
62
- # add secure method fallbacks
63
- if self.connection_method != "PLAIN":
64
- if self.connection_method != "STARTTLS":
65
- methods_to_try.append("STARTTLS")
66
- elif self.connection_method != "SSL":
67
- methods_to_try.append("SSL")
68
-
69
- for method in methods_to_try:
70
- try:
71
- if method == "STARTTLS":
72
- server = smtplib.SMTP(self.smtp_server, self.smtp_port)
73
- server.ehlo()
74
- server.starttls(context=context)
75
- server.ehlo()
76
- elif method == "SSL":
77
- server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port, context=context)
78
- server.ehlo()
79
- elif method == "PLAIN":
80
- server = smtplib.SMTP(self.smtp_server, self.smtp_port)
81
- server.ehlo()
82
- else:
83
- continue # Unsupported method
84
-
85
- if self.username and self.password:
86
- server.login(self.username, self.password)
87
-
88
- server.send_message(msg)
89
- server.quit()
90
- break # Success
91
- except Exception as e:
92
- print(f"Failed to send email using {method}: {e}")
93
- continue
94
- else:
95
- raise Exception("All connection methods failed")
96
-
97
- await asyncio.to_thread(send_email)
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>
@@ -3,9 +3,24 @@ from __future__ import annotations
3
3
  from typing import Protocol
4
4
 
5
5
 
6
- class EmailSender(Protocol):
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-BAc4OPED.js": {
3
- "file": "assets/components-BAc4OPED.js",
2
+ "_components-B6cljCxu.js": {
3
+ "file": "assets/components-B6cljCxu.js",
4
4
  "name": "components",
5
5
  "imports": [
6
- "_vendor-CEisxXSv.js",
7
- "_pages-Dz-gbBPF.js",
8
- "_vendor-arizeai-BCTsSnvS.js",
9
- "_vendor-codemirror-DIWnRs_7.js",
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-Dz-gbBPF.js": {
14
- "file": "assets/pages-Dz-gbBPF.js",
13
+ "_pages-Dhitcl5V.js": {
14
+ "file": "assets/pages-Dhitcl5V.js",
15
15
  "name": "pages",
16
16
  "imports": [
17
- "_vendor-CEisxXSv.js",
18
- "_vendor-arizeai-BCTsSnvS.js",
19
- "_components-BAc4OPED.js",
20
- "_vendor-codemirror-DIWnRs_7.js",
21
- "_vendor-recharts-Bame54mG.js"
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-CEisxXSv.js": {
25
- "file": "assets/vendor-CEisxXSv.js",
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-BCTsSnvS.js": {
39
- "file": "assets/vendor-arizeai-BCTsSnvS.js",
38
+ "_vendor-arizeai-DT8pwHfH.js": {
39
+ "file": "assets/vendor-arizeai-DT8pwHfH.js",
40
40
  "name": "vendor-arizeai",
41
41
  "imports": [
42
- "_vendor-CEisxXSv.js"
42
+ "_vendor-C3H3sezv.js"
43
43
  ]
44
44
  },
45
- "_vendor-codemirror-DIWnRs_7.js": {
46
- "file": "assets/vendor-codemirror-DIWnRs_7.js",
45
+ "_vendor-codemirror-DvimrGxD.js": {
46
+ "file": "assets/vendor-codemirror-DvimrGxD.js",
47
47
  "name": "vendor-codemirror",
48
48
  "imports": [
49
- "_vendor-CEisxXSv.js",
50
- "_vendor-shiki-Cc73E4D-.js"
49
+ "_vendor-C3H3sezv.js",
50
+ "_vendor-shiki-i05Hmswh.js"
51
51
  ]
52
52
  },
53
- "_vendor-recharts-Bame54mG.js": {
54
- "file": "assets/vendor-recharts-Bame54mG.js",
53
+ "_vendor-recharts-DuSQBcYW.js": {
54
+ "file": "assets/vendor-recharts-DuSQBcYW.js",
55
55
  "name": "vendor-recharts",
56
56
  "imports": [
57
- "_vendor-CEisxXSv.js"
57
+ "_vendor-C3H3sezv.js"
58
58
  ]
59
59
  },
60
- "_vendor-shiki-Cc73E4D-.js": {
61
- "file": "assets/vendor-shiki-Cc73E4D-.js",
60
+ "_vendor-shiki-i05Hmswh.js": {
61
+ "file": "assets/vendor-shiki-i05Hmswh.js",
62
62
  "name": "vendor-shiki",
63
63
  "imports": [
64
- "_vendor-CEisxXSv.js"
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-Du53xkjY.js",
72
+ "file": "assets/index-DfHKoAV9.js",
73
73
  "name": "index",
74
74
  "src": "index.tsx",
75
75
  "isEntry": true,
76
76
  "imports": [
77
- "_vendor-CEisxXSv.js",
78
- "_vendor-arizeai-BCTsSnvS.js",
79
- "_pages-Dz-gbBPF.js",
80
- "_components-BAc4OPED.js",
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-DIWnRs_7.js",
83
- "_vendor-shiki-Cc73E4D-.js",
84
- "_vendor-recharts-Bame54mG.js"
82
+ "_vendor-codemirror-DvimrGxD.js",
83
+ "_vendor-shiki-i05Hmswh.js",
84
+ "_vendor-recharts-DuSQBcYW.js"
85
85
  ]
86
86
  }
87
87
  }