arize-phoenix 8.28.1__py3-none-any.whl → 8.29.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.28.1.dist-info → arize_phoenix-8.29.0.dist-info}/METADATA +1 -1
- {arize_phoenix-8.28.1.dist-info → arize_phoenix-8.29.0.dist-info}/RECORD +10 -10
- phoenix/config.py +269 -3
- phoenix/server/grpc_server.py +19 -2
- phoenix/server/main.py +45 -7
- phoenix/version.py +1 -1
- {arize_phoenix-8.28.1.dist-info → arize_phoenix-8.29.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-8.28.1.dist-info → arize_phoenix-8.29.0.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-8.28.1.dist-info → arize_phoenix-8.29.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-8.28.1.dist-info → arize_phoenix-8.29.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
phoenix/__init__.py,sha256=X3eUEwd2rG8KKWWYVNNDJoqo08ihfjgHhlP29dcdNJE,5481
|
|
2
2
|
phoenix/auth.py,sha256=VVMHrWN31tln3Zo4z6ofecrV4daiqJjLd8r85mqlxek,10939
|
|
3
|
-
phoenix/config.py,sha256=
|
|
3
|
+
phoenix/config.py,sha256=2ivyDcCa1x5wxRBqDL2e-w1oE9PU_MtrJuesbVwOVto,49923
|
|
4
4
|
phoenix/datetime_utils.py,sha256=iJzNG6YJ6V7_u8B2iA7P2Z26FyxYbOPtx0dhJ7kNDHA,3398
|
|
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=x87BX7hWGQQZbrW_vrYqFR_izCGfO9gFc--JXUG4Tdk,754
|
|
9
|
-
phoenix/version.py,sha256=
|
|
9
|
+
phoenix/version.py,sha256=KdmG1qfioyP2VSWVHT4Lo5kpWmRFMD0ZIRsbxC2By4g,23
|
|
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
|
|
@@ -84,9 +84,9 @@ phoenix/server/app.py,sha256=1lPHzZwWlTpwzEaO6jmgO18Ib4ssBxoLO9fwbfT_ois,39053
|
|
|
84
84
|
phoenix/server/bearer_auth.py,sha256=9AY0-aOSHm-B7OYB-20jFA7bETAA6S1_W1za42A8Yt8,6804
|
|
85
85
|
phoenix/server/dml_event.py,sha256=MjJmVEKytq75chBOSyvYDusUnEbg1pHpIjR3pZkUaJA,2838
|
|
86
86
|
phoenix/server/dml_event_handler.py,sha256=EZLXmCvx4pJrCkz29gxwKwmvmUkTtPCHw6klR-XM8qE,8258
|
|
87
|
-
phoenix/server/grpc_server.py,sha256=
|
|
87
|
+
phoenix/server/grpc_server.py,sha256=C5fUmISgaK8yVhQQXVJZXkpEvAnpjDn53j89naVYXOI,4570
|
|
88
88
|
phoenix/server/jwt_store.py,sha256=asxzY4_ZBM2FWAMstHvhvnKUP_0AA3v3xPTL2IOgNqY,16831
|
|
89
|
-
phoenix/server/main.py,sha256=
|
|
89
|
+
phoenix/server/main.py,sha256=imlwp7zETUm6vPc77CP_BWTCIDjhmlposIHSQh4JHaM,17527
|
|
90
90
|
phoenix/server/oauth2.py,sha256=EV4wcCwG0N7cJRcfGNURdP5rZgRVCeRDvXyle19A27Y,2064
|
|
91
91
|
phoenix/server/prometheus.py,sha256=1KjvSfjSa2-BPjDybVMM_Kag316CsN-Zwt64YNr_snc,7825
|
|
92
92
|
phoenix/server/rate_limiters.py,sha256=cFc73D2NaxqNZZDbwfIDw4So-fRVOJPBtqxOZ8Qky_s,7155
|
|
@@ -368,9 +368,9 @@ phoenix/utilities/project.py,sha256=auVpARXkDb-JgeX5f2aStyFIkeKvGwN9l7qrFeJMVxI,
|
|
|
368
368
|
phoenix/utilities/re.py,sha256=6YyUWIkv0zc2SigsxfOWIHzdpjKA_TZo2iqKq7zJKvw,2081
|
|
369
369
|
phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
370
370
|
phoenix/utilities/template_formatters.py,sha256=gh9PJD6WEGw7TEYXfSst1UR4pWWwmjxMLrDVQ_CkpkQ,2779
|
|
371
|
-
arize_phoenix-8.
|
|
372
|
-
arize_phoenix-8.
|
|
373
|
-
arize_phoenix-8.
|
|
374
|
-
arize_phoenix-8.
|
|
375
|
-
arize_phoenix-8.
|
|
376
|
-
arize_phoenix-8.
|
|
371
|
+
arize_phoenix-8.29.0.dist-info/METADATA,sha256=w7VAUewJBuU9aAOBXFSKFIq927SiS8oz7ch5_fHVeWI,24478
|
|
372
|
+
arize_phoenix-8.29.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
373
|
+
arize_phoenix-8.29.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
|
|
374
|
+
arize_phoenix-8.29.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
|
|
375
|
+
arize_phoenix-8.29.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
|
|
376
|
+
arize_phoenix-8.29.0.dist-info/RECORD,,
|
phoenix/config.py
CHANGED
|
@@ -2,7 +2,7 @@ import logging
|
|
|
2
2
|
import os
|
|
3
3
|
import re
|
|
4
4
|
import tempfile
|
|
5
|
-
from dataclasses import dataclass
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
6
|
from datetime import timedelta
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from importlib.metadata import version
|
|
@@ -240,6 +240,243 @@ ENV_PHOENIX_FASTAPI_MIDDLEWARE_PATHS = "PHOENIX_FASTAPI_MIDDLEWARE_PATHS"
|
|
|
240
240
|
ENV_PHOENIX_GQL_EXTENSION_PATHS = "PHOENIX_GQL_EXTENSION_PATHS"
|
|
241
241
|
ENV_PHOENIX_GRPC_INTERCEPTOR_PATHS = "PHOENIX_GRPC_INTERCEPTOR_PATHS"
|
|
242
242
|
|
|
243
|
+
ENV_PHOENIX_TLS_ENABLED = "PHOENIX_TLS_ENABLED"
|
|
244
|
+
"""
|
|
245
|
+
Whether to enable TLS for Phoenix HTTP and gRPC servers.
|
|
246
|
+
"""
|
|
247
|
+
ENV_PHOENIX_TLS_CERT_FILE = "PHOENIX_TLS_CERT_FILE"
|
|
248
|
+
"""
|
|
249
|
+
Path to the TLS certificate file for HTTPS connections.
|
|
250
|
+
When set, Phoenix will use HTTPS instead of HTTP for all connections.
|
|
251
|
+
"""
|
|
252
|
+
ENV_PHOENIX_TLS_KEY_FILE = "PHOENIX_TLS_KEY_FILE"
|
|
253
|
+
"""
|
|
254
|
+
Path to the TLS private key file for HTTPS connections.
|
|
255
|
+
Required when PHOENIX_TLS_CERT_FILE is set.
|
|
256
|
+
"""
|
|
257
|
+
ENV_PHOENIX_TLS_KEY_FILE_PASSWORD = "PHOENIX_TLS_KEY_FILE_PASSWORD"
|
|
258
|
+
"""
|
|
259
|
+
Password for the TLS private key file if it's encrypted.
|
|
260
|
+
Only needed if the private key file requires a password.
|
|
261
|
+
"""
|
|
262
|
+
ENV_PHOENIX_TLS_CA_FILE = "PHOENIX_TLS_CA_FILE"
|
|
263
|
+
"""
|
|
264
|
+
Path to the Certificate Authority (CA) file for client certificate verification.
|
|
265
|
+
Used when PHOENIX_TLS_VERIFY_CLIENT is set to true.
|
|
266
|
+
"""
|
|
267
|
+
ENV_PHOENIX_TLS_VERIFY_CLIENT = "PHOENIX_TLS_VERIFY_CLIENT"
|
|
268
|
+
"""
|
|
269
|
+
Whether to verify client certificates for mutual TLS (mTLS) authentication.
|
|
270
|
+
When set to true, clients must provide valid certificates signed by the CA specified in
|
|
271
|
+
PHOENIX_TLS_CA_FILE.
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@dataclass(frozen=True)
|
|
276
|
+
class TLSConfig:
|
|
277
|
+
"""Configuration for TLS (Transport Layer Security) connections.
|
|
278
|
+
|
|
279
|
+
This class manages TLS certificates and private keys for secure connections.
|
|
280
|
+
It handles reading certificate and key files, and decrypting private keys
|
|
281
|
+
if they are password-protected.
|
|
282
|
+
|
|
283
|
+
Attributes:
|
|
284
|
+
cert_file: Path to the TLS certificate file
|
|
285
|
+
key_file: Path to the TLS private key file
|
|
286
|
+
key_file_password: Optional password for decrypting the private key
|
|
287
|
+
_cert_data: Cached certificate data (internal use)
|
|
288
|
+
_key_data: Cached decrypted key data (internal use)
|
|
289
|
+
_decrypted_key_data: Cached decrypted key data (internal use)
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
cert_file: Path
|
|
293
|
+
key_file: Path
|
|
294
|
+
key_file_password: Optional[str]
|
|
295
|
+
_cert_data: bytes = field(default=b"", init=False, repr=False)
|
|
296
|
+
_key_data: bytes = field(default=b"", init=False, repr=False)
|
|
297
|
+
_decrypted_key_data: Optional[bytes] = field(default=None, init=False, repr=False)
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def cert_data(self) -> bytes:
|
|
301
|
+
"""Get the certificate data, reading from file if not cached.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
bytes: The certificate data in PEM format
|
|
305
|
+
"""
|
|
306
|
+
if not self._cert_data:
|
|
307
|
+
with open(self.cert_file, "rb") as f:
|
|
308
|
+
object.__setattr__(self, "_cert_data", f.read())
|
|
309
|
+
return self._cert_data
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def key_data(self) -> bytes:
|
|
313
|
+
"""Get the decrypted key data, reading from file if not cached.
|
|
314
|
+
|
|
315
|
+
This property reads the private key file and decrypts it if a password
|
|
316
|
+
is provided. The decrypted key is cached for subsequent accesses.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
bytes: The decrypted private key data in PEM format
|
|
320
|
+
|
|
321
|
+
Raises:
|
|
322
|
+
ValueError: If the cryptography library is not installed or if
|
|
323
|
+
decryption fails
|
|
324
|
+
"""
|
|
325
|
+
if not self._key_data:
|
|
326
|
+
self._read_and_cache_key_data()
|
|
327
|
+
return self._key_data
|
|
328
|
+
|
|
329
|
+
def _read_and_cache_key_data(self) -> None:
|
|
330
|
+
"""Read and decrypt the private key file, then cache the result.
|
|
331
|
+
|
|
332
|
+
This method reads the private key file, decrypts it if a password
|
|
333
|
+
is provided, and stores the decrypted key in the _key_data attribute.
|
|
334
|
+
|
|
335
|
+
Raises:
|
|
336
|
+
ValueError: If the cryptography library is not installed or if
|
|
337
|
+
decryption fails
|
|
338
|
+
"""
|
|
339
|
+
try:
|
|
340
|
+
from cryptography.hazmat.backends import default_backend
|
|
341
|
+
from cryptography.hazmat.primitives.serialization import (
|
|
342
|
+
Encoding,
|
|
343
|
+
NoEncryption,
|
|
344
|
+
PrivateFormat,
|
|
345
|
+
load_pem_private_key,
|
|
346
|
+
)
|
|
347
|
+
except ImportError:
|
|
348
|
+
raise ValueError(
|
|
349
|
+
"The cryptography library is needed to read private keys for "
|
|
350
|
+
"TLS configuration. Please install it with: pip install cryptography"
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# First read the key file
|
|
354
|
+
with open(self.key_file, "rb") as f:
|
|
355
|
+
key_data = f.read()
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
# Convert password to bytes if it exists
|
|
359
|
+
password_bytes = self.key_file_password.encode() if self.key_file_password else None
|
|
360
|
+
|
|
361
|
+
# Load the key (decrypting if password is provided)
|
|
362
|
+
private_key = load_pem_private_key(
|
|
363
|
+
key_data,
|
|
364
|
+
password=password_bytes,
|
|
365
|
+
backend=default_backend(),
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# Convert to PEM format without encryption
|
|
369
|
+
decrypted_pem = private_key.private_bytes(
|
|
370
|
+
encoding=Encoding.PEM,
|
|
371
|
+
format=PrivateFormat.PKCS8,
|
|
372
|
+
encryption_algorithm=NoEncryption(),
|
|
373
|
+
)
|
|
374
|
+
except Exception as e:
|
|
375
|
+
raise ValueError(f"Failed to decrypt private key: {e}")
|
|
376
|
+
object.__setattr__(self, "_key_data", decrypted_pem)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@dataclass(frozen=True)
|
|
380
|
+
class TLSConfigVerifyClient(TLSConfig):
|
|
381
|
+
"""TLS configuration with client verification enabled."""
|
|
382
|
+
|
|
383
|
+
ca_file: Path
|
|
384
|
+
_ca_data: bytes = field(default=b"", init=False, repr=False)
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def ca_data(self) -> bytes:
|
|
388
|
+
"""Get the CA certificate data, reading from file if not cached."""
|
|
389
|
+
if not self._ca_data:
|
|
390
|
+
with open(self.ca_file, "rb") as f:
|
|
391
|
+
object.__setattr__(self, "_ca_data", f.read())
|
|
392
|
+
return self._ca_data
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def get_env_tls_enabled() -> bool:
|
|
396
|
+
"""
|
|
397
|
+
Gets the value of the PHOENIX_TLS_ENABLED environment variable.
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
bool: True if TLS is enabled, False otherwise. Defaults to False if the environment
|
|
401
|
+
variable is not set.
|
|
402
|
+
""" # noqa: E501
|
|
403
|
+
return _bool_val(ENV_PHOENIX_TLS_ENABLED, False)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def get_env_tls_verify_client() -> bool:
|
|
407
|
+
"""
|
|
408
|
+
Gets the value of the PHOENIX_TLS_VERIFY_CLIENT environment variable.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
bool: True if client certificate verification is enabled, False otherwise. Defaults to False
|
|
412
|
+
if the environment variable is not set.
|
|
413
|
+
""" # noqa: E501
|
|
414
|
+
return _bool_val(ENV_PHOENIX_TLS_VERIFY_CLIENT, False)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def get_env_tls_config() -> Optional[TLSConfig]:
|
|
418
|
+
"""
|
|
419
|
+
Retrieves and validates TLS configuration from environment variables.
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
Optional[TLSConfig]: A configuration object containing TLS settings, or None if TLS is disabled.
|
|
423
|
+
If client verification is enabled, returns TLSConfigVerifyClient instead.
|
|
424
|
+
|
|
425
|
+
The function reads the following environment variables:
|
|
426
|
+
- PHOENIX_TLS_ENABLED: Whether TLS is enabled (defaults to False)
|
|
427
|
+
- PHOENIX_TLS_CERT_FILE: Path to the TLS certificate file
|
|
428
|
+
- PHOENIX_TLS_KEY_FILE: Path to the TLS private key file
|
|
429
|
+
- PHOENIX_TLS_KEY_FILE_PASSWORD: Password for the TLS private key file
|
|
430
|
+
- PHOENIX_TLS_CA_FILE: Path to the Certificate Authority file (required for client verification)
|
|
431
|
+
- PHOENIX_TLS_VERIFY_CLIENT: Whether to verify client certificates
|
|
432
|
+
|
|
433
|
+
Raises:
|
|
434
|
+
ValueError: If required files are missing or don't exist when TLS is enabled
|
|
435
|
+
""" # noqa: E501
|
|
436
|
+
# Check if TLS is enabled
|
|
437
|
+
if not get_env_tls_enabled():
|
|
438
|
+
return None
|
|
439
|
+
|
|
440
|
+
# Get certificate file path if specified
|
|
441
|
+
if not (cert_file_str := getenv(ENV_PHOENIX_TLS_CERT_FILE)):
|
|
442
|
+
raise ValueError("PHOENIX_TLS_CERT_FILE must be set when PHOENIX_TLS_ENABLED is true")
|
|
443
|
+
cert_file = Path(cert_file_str)
|
|
444
|
+
|
|
445
|
+
# Get private key file path if specified
|
|
446
|
+
if not (key_file_str := getenv(ENV_PHOENIX_TLS_KEY_FILE)):
|
|
447
|
+
raise ValueError("PHOENIX_TLS_KEY_FILE must be set when PHOENIX_TLS_ENABLED is true")
|
|
448
|
+
key_file = Path(key_file_str)
|
|
449
|
+
|
|
450
|
+
# Get private key password if specified
|
|
451
|
+
key_file_password = getenv(ENV_PHOENIX_TLS_KEY_FILE_PASSWORD)
|
|
452
|
+
|
|
453
|
+
# Validate certificate and key files
|
|
454
|
+
_validate_file_exists_and_is_readable(cert_file, "certificate")
|
|
455
|
+
_validate_file_exists_and_is_readable(key_file, "key")
|
|
456
|
+
|
|
457
|
+
# If client verification is enabled, validate CA file and return TLSConfigVerifyClient
|
|
458
|
+
if get_env_tls_verify_client():
|
|
459
|
+
if not (ca_file_str := getenv(ENV_PHOENIX_TLS_CA_FILE)):
|
|
460
|
+
raise ValueError(
|
|
461
|
+
"PHOENIX_TLS_CA_FILE must be set when PHOENIX_TLS_VERIFY_CLIENT is true"
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
ca_file = Path(ca_file_str)
|
|
465
|
+
_validate_file_exists_and_is_readable(ca_file, "CA")
|
|
466
|
+
|
|
467
|
+
return TLSConfigVerifyClient(
|
|
468
|
+
cert_file=cert_file,
|
|
469
|
+
key_file=key_file,
|
|
470
|
+
key_file_password=key_file_password,
|
|
471
|
+
ca_file=ca_file,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
return TLSConfig(
|
|
475
|
+
cert_file=cert_file,
|
|
476
|
+
key_file=key_file,
|
|
477
|
+
key_file_password=key_file_password,
|
|
478
|
+
)
|
|
479
|
+
|
|
243
480
|
|
|
244
481
|
def server_instrumentation_is_enabled() -> bool:
|
|
245
482
|
return bool(
|
|
@@ -956,7 +1193,8 @@ def get_env_root_url() -> URL:
|
|
|
956
1193
|
host = get_env_host()
|
|
957
1194
|
if host == "0.0.0.0":
|
|
958
1195
|
host = "127.0.0.1"
|
|
959
|
-
|
|
1196
|
+
scheme = "https" if get_env_tls_enabled() else "http"
|
|
1197
|
+
return URL(urljoin(f"{scheme}://{host}:{get_env_port()}", get_env_host_root_path()))
|
|
960
1198
|
|
|
961
1199
|
|
|
962
1200
|
def get_base_url() -> str:
|
|
@@ -964,7 +1202,8 @@ def get_base_url() -> str:
|
|
|
964
1202
|
host = get_env_host()
|
|
965
1203
|
if host == "0.0.0.0":
|
|
966
1204
|
host = "127.0.0.1"
|
|
967
|
-
|
|
1205
|
+
scheme = "https" if get_env_tls_enabled() else "http"
|
|
1206
|
+
base_url = get_env_collector_endpoint() or f"{scheme}://{host}:{get_env_port()}"
|
|
968
1207
|
return base_url if base_url.endswith("/") else base_url + "/"
|
|
969
1208
|
|
|
970
1209
|
|
|
@@ -1159,3 +1398,30 @@ When the PHOENIX_ADMIN_SECRET is used as a bearer token in API requests, the
|
|
|
1159
1398
|
request is authenticated as the system user with the user_id set to this
|
|
1160
1399
|
SYSTEM_USER_ID value (only if this variable is not None).
|
|
1161
1400
|
"""
|
|
1401
|
+
|
|
1402
|
+
|
|
1403
|
+
def _validate_file_exists_and_is_readable(
|
|
1404
|
+
file_path: Path,
|
|
1405
|
+
description: str,
|
|
1406
|
+
check_non_empty: bool = True,
|
|
1407
|
+
) -> None:
|
|
1408
|
+
"""
|
|
1409
|
+
Validate that a file exists, is readable, and optionally has non-zero size.
|
|
1410
|
+
|
|
1411
|
+
Args:
|
|
1412
|
+
file_path: Path to the file to validate
|
|
1413
|
+
description: Description of the file for error messages (e.g., "certificate", "key", "CA")
|
|
1414
|
+
check_non_empty: Whether to check if the file has non-zero size. Defaults to True.
|
|
1415
|
+
|
|
1416
|
+
Raises:
|
|
1417
|
+
ValueError: If the path is not a file, isn't readable, or has zero size (if check_non_empty is True)
|
|
1418
|
+
""" # noqa: E501
|
|
1419
|
+
if not file_path.is_file():
|
|
1420
|
+
raise ValueError(f"{description} path is not a file: {file_path}")
|
|
1421
|
+
if check_non_empty and file_path.stat().st_size == 0:
|
|
1422
|
+
raise ValueError(f"{description} file is empty: {file_path}")
|
|
1423
|
+
try:
|
|
1424
|
+
with open(file_path, "rb") as f:
|
|
1425
|
+
f.read(1) # Read just one byte to verify readability
|
|
1426
|
+
except Exception as e:
|
|
1427
|
+
raise ValueError(f"{description} file is not readable: {e}")
|
phoenix/server/grpc_server.py
CHANGED
|
@@ -14,7 +14,11 @@ from opentelemetry.proto.collector.trace.v1.trace_service_pb2_grpc import (
|
|
|
14
14
|
from typing_extensions import TypeAlias
|
|
15
15
|
|
|
16
16
|
from phoenix.auth import CanReadToken
|
|
17
|
-
from phoenix.config import
|
|
17
|
+
from phoenix.config import (
|
|
18
|
+
TLSConfigVerifyClient,
|
|
19
|
+
get_env_grpc_port,
|
|
20
|
+
get_env_tls_config,
|
|
21
|
+
)
|
|
18
22
|
from phoenix.server.bearer_auth import ApiKeyInterceptor
|
|
19
23
|
from phoenix.trace.otel import decode_otlp_span
|
|
20
24
|
from phoenix.trace.schemas import Span
|
|
@@ -86,7 +90,20 @@ class GrpcServer:
|
|
|
86
90
|
options=(("grpc.so_reuseport", 0),),
|
|
87
91
|
interceptors=interceptors,
|
|
88
92
|
)
|
|
89
|
-
|
|
93
|
+
if tls_config := get_env_tls_config():
|
|
94
|
+
private_key_certificate_chain_pairs = [(tls_config.key_data, tls_config.cert_data)]
|
|
95
|
+
server_credentials = (
|
|
96
|
+
grpc.ssl_server_credentials(
|
|
97
|
+
private_key_certificate_chain_pairs,
|
|
98
|
+
root_certificates=tls_config.ca_data,
|
|
99
|
+
require_client_auth=True,
|
|
100
|
+
)
|
|
101
|
+
if isinstance(tls_config, TLSConfigVerifyClient)
|
|
102
|
+
else grpc.ssl_server_credentials(private_key_certificate_chain_pairs)
|
|
103
|
+
)
|
|
104
|
+
server.add_secure_port(f"[::]:{get_env_grpc_port()}", server_credentials)
|
|
105
|
+
else:
|
|
106
|
+
server.add_insecure_port(f"[::]:{get_env_grpc_port()}")
|
|
90
107
|
add_TraceServiceServicer_to_server(Servicer(self._callback), server) # type: ignore[no-untyped-call,unused-ignore]
|
|
91
108
|
await server.start()
|
|
92
109
|
self._server = server
|
phoenix/server/main.py
CHANGED
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
import sys
|
|
5
5
|
from argparse import SUPPRESS, ArgumentParser
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from ssl import CERT_REQUIRED
|
|
7
8
|
from threading import Thread
|
|
8
9
|
from time import sleep, time
|
|
9
10
|
from typing import Optional
|
|
@@ -15,6 +16,7 @@ from uvicorn import Config, Server
|
|
|
15
16
|
import phoenix.trace.v1 as pb
|
|
16
17
|
from phoenix.config import (
|
|
17
18
|
EXPORT_DIR,
|
|
19
|
+
TLSConfigVerifyClient,
|
|
18
20
|
get_env_access_token_expiry,
|
|
19
21
|
get_env_allowed_origins,
|
|
20
22
|
get_env_auth_settings,
|
|
@@ -39,6 +41,7 @@ from phoenix.config import (
|
|
|
39
41
|
get_env_smtp_port,
|
|
40
42
|
get_env_smtp_username,
|
|
41
43
|
get_env_smtp_validate_certs,
|
|
44
|
+
get_env_tls_config,
|
|
42
45
|
get_pids_path,
|
|
43
46
|
)
|
|
44
47
|
from phoenix.core.model_schema_adapter import create_model_from_inferences
|
|
@@ -98,15 +101,22 @@ _WELCOME_MESSAGE = Environment(loader=BaseLoader()).from_string("""
|
|
|
98
101
|
| 🚀 Phoenix Server 🚀
|
|
99
102
|
| Phoenix UI: {{ ui_path }}
|
|
100
103
|
| Authentication: {{ auth_enabled }}
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
{%- if tls_enabled %}
|
|
105
|
+
| TLS: Enabled
|
|
106
|
+
{%- if tls_verify_client %}
|
|
107
|
+
| TLS Client Verification: Enabled
|
|
108
|
+
{%- endif %}
|
|
109
|
+
{%- endif %}
|
|
110
|
+
{%- if allowed_origins %}
|
|
111
|
+
| Allowed Origins: {{ allowed_origins }}
|
|
112
|
+
{%- endif %}
|
|
103
113
|
| Log traces:
|
|
104
114
|
| - gRPC: {{ grpc_path }}
|
|
105
115
|
| - HTTP: {{ http_path }}
|
|
106
116
|
| Storage: {{ storage }}
|
|
107
|
-
{
|
|
117
|
+
{%- if schema %}
|
|
108
118
|
| - Schema: {{ schema }}
|
|
109
|
-
{
|
|
119
|
+
{%- endif %}
|
|
110
120
|
""")
|
|
111
121
|
|
|
112
122
|
|
|
@@ -356,16 +366,24 @@ def main() -> None:
|
|
|
356
366
|
|
|
357
367
|
allowed_origins = get_env_allowed_origins()
|
|
358
368
|
|
|
369
|
+
# Get TLS configuration
|
|
370
|
+
tls_config = get_env_tls_config()
|
|
371
|
+
tls_enabled = tls_config is not None
|
|
372
|
+
tls_verify_client = tls_enabled and isinstance(tls_config, TLSConfigVerifyClient)
|
|
373
|
+
|
|
359
374
|
# Print information about the server
|
|
360
|
-
|
|
375
|
+
scheme = "https" if tls_enabled else "http"
|
|
376
|
+
root_path = urljoin(f"{scheme}://{host}:{port}", host_root_path)
|
|
361
377
|
msg = _WELCOME_MESSAGE.render(
|
|
362
378
|
version=phoenix_version,
|
|
363
379
|
ui_path=root_path,
|
|
364
|
-
grpc_path=f"
|
|
380
|
+
grpc_path=f"{scheme}://{host}:{get_env_grpc_port()}",
|
|
365
381
|
http_path=urljoin(root_path, "v1/traces"),
|
|
366
382
|
storage=get_printable_db_url(db_connection_str),
|
|
367
383
|
schema=get_env_database_schema(),
|
|
368
384
|
auth_enabled=authentication_enabled,
|
|
385
|
+
tls_enabled=tls_enabled,
|
|
386
|
+
tls_verify_client=tls_verify_client,
|
|
369
387
|
allowed_origins=allowed_origins,
|
|
370
388
|
)
|
|
371
389
|
if sys.platform.startswith("win"):
|
|
@@ -417,7 +435,27 @@ def main() -> None:
|
|
|
417
435
|
oauth2_client_configs=get_env_oauth2_settings(),
|
|
418
436
|
allowed_origins=allowed_origins,
|
|
419
437
|
)
|
|
420
|
-
|
|
438
|
+
|
|
439
|
+
# Configure server with TLS if enabled
|
|
440
|
+
server_config = Config(
|
|
441
|
+
app=app,
|
|
442
|
+
host=host, # type: ignore[arg-type]
|
|
443
|
+
port=port,
|
|
444
|
+
root_path=host_root_path,
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
if tls_config:
|
|
448
|
+
# Configure SSL context with certificate and key
|
|
449
|
+
server_config.ssl_keyfile = str(tls_config.key_file)
|
|
450
|
+
server_config.ssl_keyfile_password = tls_config.key_file_password
|
|
451
|
+
server_config.ssl_certfile = str(tls_config.cert_file)
|
|
452
|
+
|
|
453
|
+
# If CA file is provided and client verification is enabled
|
|
454
|
+
if isinstance(tls_config, TLSConfigVerifyClient):
|
|
455
|
+
server_config.ssl_ca_certs = str(tls_config.ca_file)
|
|
456
|
+
server_config.ssl_cert_reqs = CERT_REQUIRED
|
|
457
|
+
|
|
458
|
+
server = Server(config=server_config)
|
|
421
459
|
Thread(target=_write_pid_file_when_ready, args=(server,), daemon=True).start()
|
|
422
460
|
|
|
423
461
|
try:
|
phoenix/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "8.
|
|
1
|
+
__version__ = "8.29.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|