arize-phoenix 3.16.1__py3-none-any.whl → 7.7.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 (338) hide show
  1. arize_phoenix-7.7.0.dist-info/METADATA +261 -0
  2. arize_phoenix-7.7.0.dist-info/RECORD +345 -0
  3. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.0.dist-info}/WHEEL +1 -1
  4. arize_phoenix-7.7.0.dist-info/entry_points.txt +3 -0
  5. phoenix/__init__.py +86 -14
  6. phoenix/auth.py +309 -0
  7. phoenix/config.py +675 -45
  8. phoenix/core/model.py +32 -30
  9. phoenix/core/model_schema.py +102 -109
  10. phoenix/core/model_schema_adapter.py +48 -45
  11. phoenix/datetime_utils.py +24 -3
  12. phoenix/db/README.md +54 -0
  13. phoenix/db/__init__.py +4 -0
  14. phoenix/db/alembic.ini +85 -0
  15. phoenix/db/bulk_inserter.py +294 -0
  16. phoenix/db/engines.py +208 -0
  17. phoenix/db/enums.py +20 -0
  18. phoenix/db/facilitator.py +113 -0
  19. phoenix/db/helpers.py +159 -0
  20. phoenix/db/insertion/constants.py +2 -0
  21. phoenix/db/insertion/dataset.py +227 -0
  22. phoenix/db/insertion/document_annotation.py +171 -0
  23. phoenix/db/insertion/evaluation.py +191 -0
  24. phoenix/db/insertion/helpers.py +98 -0
  25. phoenix/db/insertion/span.py +193 -0
  26. phoenix/db/insertion/span_annotation.py +158 -0
  27. phoenix/db/insertion/trace_annotation.py +158 -0
  28. phoenix/db/insertion/types.py +256 -0
  29. phoenix/db/migrate.py +86 -0
  30. phoenix/db/migrations/data_migration_scripts/populate_project_sessions.py +199 -0
  31. phoenix/db/migrations/env.py +114 -0
  32. phoenix/db/migrations/script.py.mako +26 -0
  33. phoenix/db/migrations/versions/10460e46d750_datasets.py +317 -0
  34. phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py +126 -0
  35. phoenix/db/migrations/versions/4ded9e43755f_create_project_sessions_table.py +66 -0
  36. phoenix/db/migrations/versions/cd164e83824f_users_and_tokens.py +157 -0
  37. phoenix/db/migrations/versions/cf03bd6bae1d_init.py +280 -0
  38. phoenix/db/models.py +807 -0
  39. phoenix/exceptions.py +5 -1
  40. phoenix/experiments/__init__.py +6 -0
  41. phoenix/experiments/evaluators/__init__.py +29 -0
  42. phoenix/experiments/evaluators/base.py +158 -0
  43. phoenix/experiments/evaluators/code_evaluators.py +184 -0
  44. phoenix/experiments/evaluators/llm_evaluators.py +473 -0
  45. phoenix/experiments/evaluators/utils.py +236 -0
  46. phoenix/experiments/functions.py +772 -0
  47. phoenix/experiments/tracing.py +86 -0
  48. phoenix/experiments/types.py +726 -0
  49. phoenix/experiments/utils.py +25 -0
  50. phoenix/inferences/__init__.py +0 -0
  51. phoenix/{datasets → inferences}/errors.py +6 -5
  52. phoenix/{datasets → inferences}/fixtures.py +49 -42
  53. phoenix/{datasets/dataset.py → inferences/inferences.py} +121 -105
  54. phoenix/{datasets → inferences}/schema.py +11 -11
  55. phoenix/{datasets → inferences}/validation.py +13 -14
  56. phoenix/logging/__init__.py +3 -0
  57. phoenix/logging/_config.py +90 -0
  58. phoenix/logging/_filter.py +6 -0
  59. phoenix/logging/_formatter.py +69 -0
  60. phoenix/metrics/__init__.py +5 -4
  61. phoenix/metrics/binning.py +4 -3
  62. phoenix/metrics/metrics.py +2 -1
  63. phoenix/metrics/mixins.py +7 -6
  64. phoenix/metrics/retrieval_metrics.py +2 -1
  65. phoenix/metrics/timeseries.py +5 -4
  66. phoenix/metrics/wrappers.py +9 -3
  67. phoenix/pointcloud/clustering.py +5 -5
  68. phoenix/pointcloud/pointcloud.py +7 -5
  69. phoenix/pointcloud/projectors.py +5 -6
  70. phoenix/pointcloud/umap_parameters.py +53 -52
  71. phoenix/server/api/README.md +28 -0
  72. phoenix/server/api/auth.py +44 -0
  73. phoenix/server/api/context.py +152 -9
  74. phoenix/server/api/dataloaders/__init__.py +91 -0
  75. phoenix/server/api/dataloaders/annotation_summaries.py +139 -0
  76. phoenix/server/api/dataloaders/average_experiment_run_latency.py +54 -0
  77. phoenix/server/api/dataloaders/cache/__init__.py +3 -0
  78. phoenix/server/api/dataloaders/cache/two_tier_cache.py +68 -0
  79. phoenix/server/api/dataloaders/dataset_example_revisions.py +131 -0
  80. phoenix/server/api/dataloaders/dataset_example_spans.py +38 -0
  81. phoenix/server/api/dataloaders/document_evaluation_summaries.py +144 -0
  82. phoenix/server/api/dataloaders/document_evaluations.py +31 -0
  83. phoenix/server/api/dataloaders/document_retrieval_metrics.py +89 -0
  84. phoenix/server/api/dataloaders/experiment_annotation_summaries.py +79 -0
  85. phoenix/server/api/dataloaders/experiment_error_rates.py +58 -0
  86. phoenix/server/api/dataloaders/experiment_run_annotations.py +36 -0
  87. phoenix/server/api/dataloaders/experiment_run_counts.py +49 -0
  88. phoenix/server/api/dataloaders/experiment_sequence_number.py +44 -0
  89. phoenix/server/api/dataloaders/latency_ms_quantile.py +188 -0
  90. phoenix/server/api/dataloaders/min_start_or_max_end_times.py +85 -0
  91. phoenix/server/api/dataloaders/project_by_name.py +31 -0
  92. phoenix/server/api/dataloaders/record_counts.py +116 -0
  93. phoenix/server/api/dataloaders/session_io.py +79 -0
  94. phoenix/server/api/dataloaders/session_num_traces.py +30 -0
  95. phoenix/server/api/dataloaders/session_num_traces_with_error.py +32 -0
  96. phoenix/server/api/dataloaders/session_token_usages.py +41 -0
  97. phoenix/server/api/dataloaders/session_trace_latency_ms_quantile.py +55 -0
  98. phoenix/server/api/dataloaders/span_annotations.py +26 -0
  99. phoenix/server/api/dataloaders/span_dataset_examples.py +31 -0
  100. phoenix/server/api/dataloaders/span_descendants.py +57 -0
  101. phoenix/server/api/dataloaders/span_projects.py +33 -0
  102. phoenix/server/api/dataloaders/token_counts.py +124 -0
  103. phoenix/server/api/dataloaders/trace_by_trace_ids.py +25 -0
  104. phoenix/server/api/dataloaders/trace_root_spans.py +32 -0
  105. phoenix/server/api/dataloaders/user_roles.py +30 -0
  106. phoenix/server/api/dataloaders/users.py +33 -0
  107. phoenix/server/api/exceptions.py +48 -0
  108. phoenix/server/api/helpers/__init__.py +12 -0
  109. phoenix/server/api/helpers/dataset_helpers.py +217 -0
  110. phoenix/server/api/helpers/experiment_run_filters.py +763 -0
  111. phoenix/server/api/helpers/playground_clients.py +948 -0
  112. phoenix/server/api/helpers/playground_registry.py +70 -0
  113. phoenix/server/api/helpers/playground_spans.py +455 -0
  114. phoenix/server/api/input_types/AddExamplesToDatasetInput.py +16 -0
  115. phoenix/server/api/input_types/AddSpansToDatasetInput.py +14 -0
  116. phoenix/server/api/input_types/ChatCompletionInput.py +38 -0
  117. phoenix/server/api/input_types/ChatCompletionMessageInput.py +24 -0
  118. phoenix/server/api/input_types/ClearProjectInput.py +15 -0
  119. phoenix/server/api/input_types/ClusterInput.py +2 -2
  120. phoenix/server/api/input_types/CreateDatasetInput.py +12 -0
  121. phoenix/server/api/input_types/CreateSpanAnnotationInput.py +18 -0
  122. phoenix/server/api/input_types/CreateTraceAnnotationInput.py +18 -0
  123. phoenix/server/api/input_types/DataQualityMetricInput.py +5 -2
  124. phoenix/server/api/input_types/DatasetExampleInput.py +14 -0
  125. phoenix/server/api/input_types/DatasetSort.py +17 -0
  126. phoenix/server/api/input_types/DatasetVersionSort.py +16 -0
  127. phoenix/server/api/input_types/DeleteAnnotationsInput.py +7 -0
  128. phoenix/server/api/input_types/DeleteDatasetExamplesInput.py +13 -0
  129. phoenix/server/api/input_types/DeleteDatasetInput.py +7 -0
  130. phoenix/server/api/input_types/DeleteExperimentsInput.py +7 -0
  131. phoenix/server/api/input_types/DimensionFilter.py +4 -4
  132. phoenix/server/api/input_types/GenerativeModelInput.py +17 -0
  133. phoenix/server/api/input_types/Granularity.py +1 -1
  134. phoenix/server/api/input_types/InvocationParameters.py +162 -0
  135. phoenix/server/api/input_types/PatchAnnotationInput.py +19 -0
  136. phoenix/server/api/input_types/PatchDatasetExamplesInput.py +35 -0
  137. phoenix/server/api/input_types/PatchDatasetInput.py +14 -0
  138. phoenix/server/api/input_types/PerformanceMetricInput.py +5 -2
  139. phoenix/server/api/input_types/ProjectSessionSort.py +29 -0
  140. phoenix/server/api/input_types/SpanAnnotationSort.py +17 -0
  141. phoenix/server/api/input_types/SpanSort.py +134 -69
  142. phoenix/server/api/input_types/TemplateOptions.py +10 -0
  143. phoenix/server/api/input_types/TraceAnnotationSort.py +17 -0
  144. phoenix/server/api/input_types/UserRoleInput.py +9 -0
  145. phoenix/server/api/mutations/__init__.py +28 -0
  146. phoenix/server/api/mutations/api_key_mutations.py +167 -0
  147. phoenix/server/api/mutations/chat_mutations.py +593 -0
  148. phoenix/server/api/mutations/dataset_mutations.py +591 -0
  149. phoenix/server/api/mutations/experiment_mutations.py +75 -0
  150. phoenix/server/api/{types/ExportEventsMutation.py → mutations/export_events_mutations.py} +21 -18
  151. phoenix/server/api/mutations/project_mutations.py +57 -0
  152. phoenix/server/api/mutations/span_annotations_mutations.py +128 -0
  153. phoenix/server/api/mutations/trace_annotations_mutations.py +127 -0
  154. phoenix/server/api/mutations/user_mutations.py +329 -0
  155. phoenix/server/api/openapi/__init__.py +0 -0
  156. phoenix/server/api/openapi/main.py +17 -0
  157. phoenix/server/api/openapi/schema.py +16 -0
  158. phoenix/server/api/queries.py +738 -0
  159. phoenix/server/api/routers/__init__.py +11 -0
  160. phoenix/server/api/routers/auth.py +284 -0
  161. phoenix/server/api/routers/embeddings.py +26 -0
  162. phoenix/server/api/routers/oauth2.py +488 -0
  163. phoenix/server/api/routers/v1/__init__.py +64 -0
  164. phoenix/server/api/routers/v1/datasets.py +1017 -0
  165. phoenix/server/api/routers/v1/evaluations.py +362 -0
  166. phoenix/server/api/routers/v1/experiment_evaluations.py +115 -0
  167. phoenix/server/api/routers/v1/experiment_runs.py +167 -0
  168. phoenix/server/api/routers/v1/experiments.py +308 -0
  169. phoenix/server/api/routers/v1/pydantic_compat.py +78 -0
  170. phoenix/server/api/routers/v1/spans.py +267 -0
  171. phoenix/server/api/routers/v1/traces.py +208 -0
  172. phoenix/server/api/routers/v1/utils.py +95 -0
  173. phoenix/server/api/schema.py +44 -241
  174. phoenix/server/api/subscriptions.py +597 -0
  175. phoenix/server/api/types/Annotation.py +21 -0
  176. phoenix/server/api/types/AnnotationSummary.py +55 -0
  177. phoenix/server/api/types/AnnotatorKind.py +16 -0
  178. phoenix/server/api/types/ApiKey.py +27 -0
  179. phoenix/server/api/types/AuthMethod.py +9 -0
  180. phoenix/server/api/types/ChatCompletionMessageRole.py +11 -0
  181. phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +46 -0
  182. phoenix/server/api/types/Cluster.py +25 -24
  183. phoenix/server/api/types/CreateDatasetPayload.py +8 -0
  184. phoenix/server/api/types/DataQualityMetric.py +31 -13
  185. phoenix/server/api/types/Dataset.py +288 -63
  186. phoenix/server/api/types/DatasetExample.py +85 -0
  187. phoenix/server/api/types/DatasetExampleRevision.py +34 -0
  188. phoenix/server/api/types/DatasetVersion.py +14 -0
  189. phoenix/server/api/types/Dimension.py +32 -31
  190. phoenix/server/api/types/DocumentEvaluationSummary.py +9 -8
  191. phoenix/server/api/types/EmbeddingDimension.py +56 -49
  192. phoenix/server/api/types/Evaluation.py +25 -31
  193. phoenix/server/api/types/EvaluationSummary.py +30 -50
  194. phoenix/server/api/types/Event.py +20 -20
  195. phoenix/server/api/types/ExampleRevisionInterface.py +14 -0
  196. phoenix/server/api/types/Experiment.py +152 -0
  197. phoenix/server/api/types/ExperimentAnnotationSummary.py +13 -0
  198. phoenix/server/api/types/ExperimentComparison.py +17 -0
  199. phoenix/server/api/types/ExperimentRun.py +119 -0
  200. phoenix/server/api/types/ExperimentRunAnnotation.py +56 -0
  201. phoenix/server/api/types/GenerativeModel.py +9 -0
  202. phoenix/server/api/types/GenerativeProvider.py +85 -0
  203. phoenix/server/api/types/Inferences.py +80 -0
  204. phoenix/server/api/types/InferencesRole.py +23 -0
  205. phoenix/server/api/types/LabelFraction.py +7 -0
  206. phoenix/server/api/types/MimeType.py +2 -2
  207. phoenix/server/api/types/Model.py +54 -54
  208. phoenix/server/api/types/PerformanceMetric.py +8 -5
  209. phoenix/server/api/types/Project.py +407 -142
  210. phoenix/server/api/types/ProjectSession.py +139 -0
  211. phoenix/server/api/types/Segments.py +4 -4
  212. phoenix/server/api/types/Span.py +221 -176
  213. phoenix/server/api/types/SpanAnnotation.py +43 -0
  214. phoenix/server/api/types/SpanIOValue.py +15 -0
  215. phoenix/server/api/types/SystemApiKey.py +9 -0
  216. phoenix/server/api/types/TemplateLanguage.py +10 -0
  217. phoenix/server/api/types/TimeSeries.py +19 -15
  218. phoenix/server/api/types/TokenUsage.py +11 -0
  219. phoenix/server/api/types/Trace.py +154 -0
  220. phoenix/server/api/types/TraceAnnotation.py +45 -0
  221. phoenix/server/api/types/UMAPPoints.py +7 -7
  222. phoenix/server/api/types/User.py +60 -0
  223. phoenix/server/api/types/UserApiKey.py +45 -0
  224. phoenix/server/api/types/UserRole.py +15 -0
  225. phoenix/server/api/types/node.py +4 -112
  226. phoenix/server/api/types/pagination.py +156 -57
  227. phoenix/server/api/utils.py +34 -0
  228. phoenix/server/app.py +864 -115
  229. phoenix/server/bearer_auth.py +163 -0
  230. phoenix/server/dml_event.py +136 -0
  231. phoenix/server/dml_event_handler.py +256 -0
  232. phoenix/server/email/__init__.py +0 -0
  233. phoenix/server/email/sender.py +97 -0
  234. phoenix/server/email/templates/__init__.py +0 -0
  235. phoenix/server/email/templates/password_reset.html +19 -0
  236. phoenix/server/email/types.py +11 -0
  237. phoenix/server/grpc_server.py +102 -0
  238. phoenix/server/jwt_store.py +505 -0
  239. phoenix/server/main.py +305 -116
  240. phoenix/server/oauth2.py +52 -0
  241. phoenix/server/openapi/__init__.py +0 -0
  242. phoenix/server/prometheus.py +111 -0
  243. phoenix/server/rate_limiters.py +188 -0
  244. phoenix/server/static/.vite/manifest.json +87 -0
  245. phoenix/server/static/assets/components-Cy9nwIvF.js +2125 -0
  246. phoenix/server/static/assets/index-BKvHIxkk.js +113 -0
  247. phoenix/server/static/assets/pages-CUi2xCVQ.js +4449 -0
  248. phoenix/server/static/assets/vendor-DvC8cT4X.js +894 -0
  249. phoenix/server/static/assets/vendor-DxkFTwjz.css +1 -0
  250. phoenix/server/static/assets/vendor-arizeai-Do1793cv.js +662 -0
  251. phoenix/server/static/assets/vendor-codemirror-BzwZPyJM.js +24 -0
  252. phoenix/server/static/assets/vendor-recharts-_Jb7JjhG.js +59 -0
  253. phoenix/server/static/assets/vendor-shiki-Cl9QBraO.js +5 -0
  254. phoenix/server/static/assets/vendor-three-DwGkEfCM.js +2998 -0
  255. phoenix/server/telemetry.py +68 -0
  256. phoenix/server/templates/index.html +82 -23
  257. phoenix/server/thread_server.py +3 -3
  258. phoenix/server/types.py +275 -0
  259. phoenix/services.py +27 -18
  260. phoenix/session/client.py +743 -68
  261. phoenix/session/data_extractor.py +31 -7
  262. phoenix/session/evaluation.py +3 -9
  263. phoenix/session/session.py +263 -219
  264. phoenix/settings.py +22 -0
  265. phoenix/trace/__init__.py +2 -22
  266. phoenix/trace/attributes.py +338 -0
  267. phoenix/trace/dsl/README.md +116 -0
  268. phoenix/trace/dsl/filter.py +663 -213
  269. phoenix/trace/dsl/helpers.py +73 -21
  270. phoenix/trace/dsl/query.py +574 -201
  271. phoenix/trace/exporter.py +24 -19
  272. phoenix/trace/fixtures.py +368 -32
  273. phoenix/trace/otel.py +71 -219
  274. phoenix/trace/projects.py +3 -2
  275. phoenix/trace/schemas.py +33 -11
  276. phoenix/trace/span_evaluations.py +21 -16
  277. phoenix/trace/span_json_decoder.py +6 -4
  278. phoenix/trace/span_json_encoder.py +2 -2
  279. phoenix/trace/trace_dataset.py +47 -32
  280. phoenix/trace/utils.py +21 -4
  281. phoenix/utilities/__init__.py +0 -26
  282. phoenix/utilities/client.py +132 -0
  283. phoenix/utilities/deprecation.py +31 -0
  284. phoenix/utilities/error_handling.py +3 -2
  285. phoenix/utilities/json.py +109 -0
  286. phoenix/utilities/logging.py +8 -0
  287. phoenix/utilities/project.py +2 -2
  288. phoenix/utilities/re.py +49 -0
  289. phoenix/utilities/span_store.py +0 -23
  290. phoenix/utilities/template_formatters.py +99 -0
  291. phoenix/version.py +1 -1
  292. arize_phoenix-3.16.1.dist-info/METADATA +0 -495
  293. arize_phoenix-3.16.1.dist-info/RECORD +0 -178
  294. phoenix/core/project.py +0 -619
  295. phoenix/core/traces.py +0 -96
  296. phoenix/experimental/evals/__init__.py +0 -73
  297. phoenix/experimental/evals/evaluators.py +0 -413
  298. phoenix/experimental/evals/functions/__init__.py +0 -4
  299. phoenix/experimental/evals/functions/classify.py +0 -453
  300. phoenix/experimental/evals/functions/executor.py +0 -353
  301. phoenix/experimental/evals/functions/generate.py +0 -138
  302. phoenix/experimental/evals/functions/processing.py +0 -76
  303. phoenix/experimental/evals/models/__init__.py +0 -14
  304. phoenix/experimental/evals/models/anthropic.py +0 -175
  305. phoenix/experimental/evals/models/base.py +0 -170
  306. phoenix/experimental/evals/models/bedrock.py +0 -221
  307. phoenix/experimental/evals/models/litellm.py +0 -134
  308. phoenix/experimental/evals/models/openai.py +0 -448
  309. phoenix/experimental/evals/models/rate_limiters.py +0 -246
  310. phoenix/experimental/evals/models/vertex.py +0 -173
  311. phoenix/experimental/evals/models/vertexai.py +0 -186
  312. phoenix/experimental/evals/retrievals.py +0 -96
  313. phoenix/experimental/evals/templates/__init__.py +0 -50
  314. phoenix/experimental/evals/templates/default_templates.py +0 -472
  315. phoenix/experimental/evals/templates/template.py +0 -195
  316. phoenix/experimental/evals/utils/__init__.py +0 -172
  317. phoenix/experimental/evals/utils/threads.py +0 -27
  318. phoenix/server/api/helpers.py +0 -11
  319. phoenix/server/api/routers/evaluation_handler.py +0 -109
  320. phoenix/server/api/routers/span_handler.py +0 -70
  321. phoenix/server/api/routers/trace_handler.py +0 -60
  322. phoenix/server/api/types/DatasetRole.py +0 -23
  323. phoenix/server/static/index.css +0 -6
  324. phoenix/server/static/index.js +0 -7447
  325. phoenix/storage/span_store/__init__.py +0 -23
  326. phoenix/storage/span_store/text_file.py +0 -85
  327. phoenix/trace/dsl/missing.py +0 -60
  328. phoenix/trace/langchain/__init__.py +0 -3
  329. phoenix/trace/langchain/instrumentor.py +0 -35
  330. phoenix/trace/llama_index/__init__.py +0 -3
  331. phoenix/trace/llama_index/callback.py +0 -102
  332. phoenix/trace/openai/__init__.py +0 -3
  333. phoenix/trace/openai/instrumentor.py +0 -30
  334. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.0.dist-info}/licenses/IP_NOTICE +0 -0
  335. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.0.dist-info}/licenses/LICENSE +0 -0
  336. /phoenix/{datasets → db/insertion}/__init__.py +0 -0
  337. /phoenix/{experimental → db/migrations}/__init__.py +0 -0
  338. /phoenix/{storage → db/migrations/data_migration_scripts}/__init__.py +0 -0
phoenix/server/main.py CHANGED
@@ -1,54 +1,87 @@
1
1
  import atexit
2
- import logging
2
+ import codecs
3
3
  import os
4
- from argparse import ArgumentParser
4
+ import sys
5
+ from argparse import SUPPRESS, ArgumentParser
5
6
  from pathlib import Path
6
- from random import random
7
7
  from threading import Thread
8
8
  from time import sleep, time
9
- from typing import Iterable, Optional, Protocol, TypeVar
9
+ from typing import Optional
10
+ from urllib.parse import urljoin
10
11
 
11
- import pkg_resources
12
+ from jinja2 import BaseLoader, Environment
12
13
  from uvicorn import Config, Server
13
14
 
15
+ import phoenix.trace.v1 as pb
14
16
  from phoenix.config import (
15
17
  EXPORT_DIR,
18
+ get_env_access_token_expiry,
19
+ get_env_auth_settings,
20
+ get_env_database_connection_str,
21
+ get_env_database_schema,
22
+ get_env_db_logging_level,
23
+ get_env_enable_prometheus,
24
+ get_env_enable_websockets,
25
+ get_env_grpc_port,
16
26
  get_env_host,
27
+ get_env_host_root_path,
28
+ get_env_log_migrations,
29
+ get_env_logging_level,
30
+ get_env_logging_mode,
31
+ get_env_oauth2_settings,
32
+ get_env_password_reset_token_expiry,
17
33
  get_env_port,
34
+ get_env_refresh_token_expiry,
35
+ get_env_smtp_hostname,
36
+ get_env_smtp_mail_from,
37
+ get_env_smtp_password,
38
+ get_env_smtp_port,
39
+ get_env_smtp_username,
40
+ get_env_smtp_validate_certs,
18
41
  get_pids_path,
19
42
  )
20
- from phoenix.core.model_schema_adapter import create_model_from_datasets
21
- from phoenix.core.traces import Traces
22
- from phoenix.datasets.dataset import EMPTY_DATASET, Dataset
23
- from phoenix.datasets.fixtures import FIXTURES, get_datasets
43
+ from phoenix.core.model_schema_adapter import create_model_from_inferences
44
+ from phoenix.db import get_printable_db_url
45
+ from phoenix.inferences.fixtures import FIXTURES, get_inferences
46
+ from phoenix.inferences.inferences import EMPTY_INFERENCES, Inferences
47
+ from phoenix.logging import setup_logging
24
48
  from phoenix.pointcloud.umap_parameters import (
25
49
  DEFAULT_MIN_DIST,
26
50
  DEFAULT_N_NEIGHBORS,
27
51
  DEFAULT_N_SAMPLES,
28
52
  UMAPParameters,
29
53
  )
30
- from phoenix.server.app import create_app
31
- from phoenix.storage.span_store import SpanStore
54
+ from phoenix.server.app import (
55
+ ScaffolderConfig,
56
+ _db,
57
+ create_app,
58
+ create_engine_and_run_migrations,
59
+ instrument_engine_if_enabled,
60
+ )
61
+ from phoenix.server.email.sender import SimpleEmailSender
62
+ from phoenix.server.types import DbSessionFactory
63
+ from phoenix.settings import Settings
32
64
  from phoenix.trace.fixtures import (
33
65
  TRACES_FIXTURES,
34
- _download_traces_fixture,
35
- _get_trace_fixture_by_name,
66
+ get_dataset_fixtures,
36
67
  get_evals_from_fixture,
68
+ get_trace_fixtures_by_project_name,
69
+ load_example_traces,
70
+ reset_fixture_span_ids_and_timestamps,
71
+ send_dataset_fixtures,
37
72
  )
38
- from phoenix.trace.otel import decode, encode
39
- from phoenix.trace.span_json_decoder import json_string_to_span
40
- from phoenix.utilities.span_store import get_span_store, load_traces_data_from_store
41
-
42
- logger = logging.getLogger(__name__)
73
+ from phoenix.trace.otel import decode_otlp_span, encode_span_to_otlp
74
+ from phoenix.trace.schemas import Span
75
+ from phoenix.version import __version__ as phoenix_version
43
76
 
44
- _WELCOME_MESSAGE = """
77
+ _WELCOME_MESSAGE = Environment(loader=BaseLoader()).from_string("""
45
78
 
46
79
  ██████╗ ██╗ ██╗ ██████╗ ███████╗███╗ ██╗██╗██╗ ██╗
47
80
  ██╔══██╗██║ ██║██╔═══██╗██╔════╝████╗ ██║██║╚██╗██╔╝
48
81
  ██████╔╝███████║██║ ██║█████╗ ██╔██╗ ██║██║ ╚███╔╝
49
82
  ██╔═══╝ ██╔══██║██║ ██║██╔══╝ ██║╚██╗██║██║ ██╔██╗
50
83
  ██║ ██║ ██║╚██████╔╝███████╗██║ ╚████║██║██╔╝ ██╗
51
- ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝ v{0}
84
+ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝ v{{ version }}
52
85
 
53
86
  |
54
87
  | 🌎 Join our Community 🌎
@@ -61,15 +94,22 @@ _WELCOME_MESSAGE = """
61
94
  | https://docs.arize.com/phoenix
62
95
  |
63
96
  | 🚀 Phoenix Server 🚀
64
- | Phoenix UI: http://{1}:{2}
65
- | Log traces: /v1/traces over HTTP
66
- |
67
- """
97
+ | Phoenix UI: {{ ui_path }}
98
+ | Authentication: {{ auth_enabled }}
99
+ | Websockets: {{ websockets_enabled }}
100
+ | Log traces:
101
+ | - gRPC: {{ grpc_path }}
102
+ | - HTTP: {{ http_path }}
103
+ | Storage: {{ storage }}
104
+ {% if schema -%}
105
+ | - Schema: {{ schema }}
106
+ {% endif -%}
107
+ """)
68
108
 
69
109
 
70
110
  def _write_pid_file_when_ready(
71
111
  server: Server,
72
- wait_up_to_seconds: float = 5,
112
+ wait_up_to_seconds: float = 60,
73
113
  ) -> None:
74
114
  """Write PID file after server is started (or when time is up)."""
75
115
  time_limit = time() + wait_up_to_seconds
@@ -88,161 +128,310 @@ def _get_pid_file() -> Path:
88
128
  return get_pids_path() / str(os.getpid())
89
129
 
90
130
 
91
- _Item = TypeVar("_Item", contravariant=True)
92
-
93
-
94
- class _SupportsPut(Protocol[_Item]):
95
- def put(self, item: _Item) -> None: ...
96
-
97
-
98
- def _load_items(
99
- queue: _SupportsPut[_Item],
100
- items: Iterable[_Item],
101
- simulate_streaming: Optional[bool] = False,
102
- ) -> None:
103
- for item in items:
104
- if simulate_streaming:
105
- sleep(random())
106
- queue.put(item)
131
+ DEFAULT_UMAP_PARAMS_STR = f"{DEFAULT_MIN_DIST},{DEFAULT_N_NEIGHBORS},{DEFAULT_N_SAMPLES}"
107
132
 
108
133
 
109
- DEFAULT_UMAP_PARAMS_STR = f"{DEFAULT_MIN_DIST},{DEFAULT_N_NEIGHBORS},{DEFAULT_N_SAMPLES}"
134
+ def main() -> None:
135
+ initialize_settings()
136
+ setup_logging()
110
137
 
111
- if __name__ == "__main__":
112
- primary_dataset_name: str
113
- reference_dataset_name: Optional[str]
138
+ primary_inferences_name: str
139
+ reference_inferences_name: Optional[str]
114
140
  trace_dataset_name: Optional[str] = None
115
- simulate_streaming: Optional[bool] = None
116
141
 
117
- primary_dataset: Dataset = EMPTY_DATASET
118
- reference_dataset: Optional[Dataset] = None
119
- corpus_dataset: Optional[Dataset] = None
142
+ primary_inferences: Inferences = EMPTY_INFERENCES
143
+ reference_inferences: Optional[Inferences] = None
144
+ corpus_inferences: Optional[Inferences] = None
120
145
 
121
- # automatically remove the pid file when the process is being gracefully terminated
122
146
  atexit.register(_remove_pid_file)
123
147
 
124
- parser = ArgumentParser()
125
- parser.add_argument("--export_path")
126
- parser.add_argument("--host", type=str, required=False)
127
- parser.add_argument("--port", type=int, required=False)
128
- parser.add_argument("--read-only", type=bool, default=False)
129
- parser.add_argument("--no-internet", action="store_true")
130
- parser.add_argument("--umap_params", type=str, required=False, default=DEFAULT_UMAP_PARAMS_STR)
131
- parser.add_argument("--debug", action="store_false")
132
- subparsers = parser.add_subparsers(dest="command", required=True)
148
+ parser = ArgumentParser(usage="phoenix serve", add_help=False)
149
+ parser.add_argument(
150
+ "-h",
151
+ "--help",
152
+ action="help",
153
+ help=SUPPRESS,
154
+ )
155
+ parser.add_argument("--database-url", required=False, help=SUPPRESS)
156
+ parser.add_argument("--export_path", help=SUPPRESS)
157
+ parser.add_argument("--host", type=str, required=False, help=SUPPRESS)
158
+ parser.add_argument("--port", type=int, required=False, help=SUPPRESS)
159
+ parser.add_argument("--read-only", action="store_true", required=False, help=SUPPRESS)
160
+ parser.add_argument("--no-internet", action="store_true", help=SUPPRESS)
161
+ parser.add_argument(
162
+ "--umap_params", type=str, required=False, default=DEFAULT_UMAP_PARAMS_STR, help=SUPPRESS
163
+ )
164
+ parser.add_argument("--debug", action="store_true", help=SUPPRESS)
165
+ parser.add_argument("--dev", action="store_true", help=SUPPRESS)
166
+ parser.add_argument("--no-ui", action="store_true", help=SUPPRESS)
167
+ parser.add_argument("--enable-websockets", type=str, help=SUPPRESS)
168
+ subparsers = parser.add_subparsers(dest="command", required=True, help=SUPPRESS)
169
+
133
170
  serve_parser = subparsers.add_parser("serve")
171
+ serve_parser.add_argument(
172
+ "--with-fixture",
173
+ type=str,
174
+ required=False,
175
+ default="",
176
+ help=("Name of an inference fixture. Example: 'fixture1'"),
177
+ )
178
+ serve_parser.add_argument(
179
+ "--with-trace-fixtures",
180
+ type=str,
181
+ required=False,
182
+ default="",
183
+ help=(
184
+ "Comma separated list of tracing fixture names (spaces are ignored). "
185
+ "Example: 'fixture1, fixture2'"
186
+ ),
187
+ )
188
+ serve_parser.add_argument(
189
+ "--with-projects",
190
+ type=str,
191
+ required=False,
192
+ default="",
193
+ help=(
194
+ "Comma separated list of project names (spaces are ignored). "
195
+ "Example: 'project1, project2'"
196
+ ),
197
+ )
198
+ serve_parser.add_argument(
199
+ "--force-fixture-ingestion",
200
+ action="store_true",
201
+ required=False,
202
+ help=(
203
+ "Whether or not to check the database age before adding the fixtures. "
204
+ "Default is False, i.e., fixtures will only be added if the "
205
+ "database is new."
206
+ ),
207
+ )
208
+ serve_parser.add_argument(
209
+ "--scaffold-datasets",
210
+ action="store_true",
211
+ required=False,
212
+ help=(
213
+ "Whether or not to add any datasets defined in "
214
+ "the inputted project or trace fixture. "
215
+ "Default is False. "
216
+ ),
217
+ )
218
+
134
219
  datasets_parser = subparsers.add_parser("datasets")
135
220
  datasets_parser.add_argument("--primary", type=str, required=True)
136
221
  datasets_parser.add_argument("--reference", type=str, required=False)
137
222
  datasets_parser.add_argument("--corpus", type=str, required=False)
138
223
  datasets_parser.add_argument("--trace", type=str, required=False)
224
+
139
225
  fixture_parser = subparsers.add_parser("fixture")
140
226
  fixture_parser.add_argument("fixture", type=str, choices=[fixture.name for fixture in FIXTURES])
141
- fixture_parser.add_argument("--primary-only", type=bool)
227
+ fixture_parser.add_argument("--primary-only", action="store_true")
228
+
142
229
  trace_fixture_parser = subparsers.add_parser("trace-fixture")
143
230
  trace_fixture_parser.add_argument(
144
231
  "fixture", type=str, choices=[fixture.name for fixture in TRACES_FIXTURES]
145
232
  )
146
- trace_fixture_parser.add_argument("--simulate-streaming", type=bool)
233
+ trace_fixture_parser.add_argument("--simulate-streaming", action="store_true")
234
+
147
235
  demo_parser = subparsers.add_parser("demo")
148
236
  demo_parser.add_argument("fixture", type=str, choices=[fixture.name for fixture in FIXTURES])
149
237
  demo_parser.add_argument(
150
238
  "trace_fixture", type=str, choices=[fixture.name for fixture in TRACES_FIXTURES]
151
239
  )
152
240
  demo_parser.add_argument("--simulate-streaming", action="store_true")
241
+
153
242
  args = parser.parse_args()
154
- export_path = Path(args.export_path) if args.export_path else EXPORT_DIR
155
- span_store: Optional[SpanStore] = None
243
+ db_connection_str = (
244
+ args.database_url if args.database_url else get_env_database_connection_str()
245
+ )
246
+ export_path = Path(args.export_path) if args.export_path else Path(EXPORT_DIR)
247
+
248
+ force_fixture_ingestion = False
249
+ scaffold_datasets = False
250
+ tracing_fixture_names = set()
156
251
  if args.command == "datasets":
157
- primary_dataset_name = args.primary
158
- reference_dataset_name = args.reference
159
- corpus_dataset_name = args.corpus
160
- primary_dataset = Dataset.from_name(primary_dataset_name)
161
- reference_dataset = (
162
- Dataset.from_name(reference_dataset_name)
163
- if reference_dataset_name is not None
252
+ primary_inferences_name = args.primary
253
+ reference_inferences_name = args.reference
254
+ corpus_inferences_name = args.corpus
255
+ primary_inferences = Inferences.from_name(primary_inferences_name)
256
+ reference_inferences = (
257
+ Inferences.from_name(reference_inferences_name)
258
+ if reference_inferences_name is not None
164
259
  else None
165
260
  )
166
- corpus_dataset = (
167
- None if corpus_dataset_name is None else Dataset.from_name(corpus_dataset_name)
261
+ corpus_inferences = (
262
+ None if corpus_inferences_name is None else Inferences.from_name(corpus_inferences_name)
168
263
  )
169
264
  elif args.command == "fixture":
170
265
  fixture_name = args.fixture
171
266
  primary_only = args.primary_only
172
- primary_dataset, reference_dataset, corpus_dataset = get_datasets(
267
+ primary_inferences, reference_inferences, corpus_inferences = get_inferences(
173
268
  fixture_name,
174
269
  args.no_internet,
175
270
  )
176
271
  if primary_only:
177
- reference_dataset_name = None
178
- reference_dataset = None
272
+ reference_inferences_name = None
273
+ reference_inferences = None
179
274
  elif args.command == "trace-fixture":
180
275
  trace_dataset_name = args.fixture
181
- simulate_streaming = args.simulate_streaming
182
276
  elif args.command == "demo":
183
277
  fixture_name = args.fixture
184
- primary_dataset, reference_dataset, corpus_dataset = get_datasets(
278
+ primary_inferences, reference_inferences, corpus_inferences = get_inferences(
185
279
  fixture_name,
186
280
  args.no_internet,
187
281
  )
188
282
  trace_dataset_name = args.trace_fixture
189
- simulate_streaming = args.simulate_streaming
283
+ elif args.command == "serve":
284
+ if args.with_fixture:
285
+ primary_inferences, reference_inferences, corpus_inferences = get_inferences(
286
+ str(args.with_fixture),
287
+ args.no_internet,
288
+ )
289
+ if args.with_trace_fixtures:
290
+ tracing_fixture_names.update(
291
+ [name.strip() for name in args.with_trace_fixtures.split(",")]
292
+ )
293
+ if args.with_projects:
294
+ project_names = [name.strip() for name in args.with_projects.split(",")]
295
+ tracing_fixture_names.update(
296
+ fixture.name
297
+ for name in project_names
298
+ for fixture in get_trace_fixtures_by_project_name(name)
299
+ )
300
+ force_fixture_ingestion = args.force_fixture_ingestion
301
+ scaffold_datasets = args.scaffold_datasets
302
+ host: Optional[str] = args.host or get_env_host()
303
+ if host == "::":
304
+ host = None
305
+
306
+ port = args.port or get_env_port()
307
+ host_root_path = get_env_host_root_path()
308
+ read_only = args.read_only
190
309
 
191
- model = create_model_from_datasets(
192
- primary_dataset,
193
- reference_dataset,
310
+ model = create_model_from_inferences(
311
+ primary_inferences,
312
+ reference_inferences,
194
313
  )
195
- traces = Traces()
196
- if span_store := get_span_store():
197
- Thread(target=load_traces_data_from_store, args=(traces, span_store), daemon=True).start()
314
+
315
+ authentication_enabled, secret = get_env_auth_settings()
316
+
317
+ fixture_spans: list[Span] = []
318
+ fixture_evals: list[pb.Evaluation] = []
198
319
  if trace_dataset_name is not None:
199
- fixture_spans = list(
200
- # Apply `encode` here because legacy jsonl files contains UUIDs as strings.
201
- # `encode` removes the hyphens in the UUIDs.
202
- decode(encode(json_string_to_span(json_span)))
203
- for json_span in _download_traces_fixture(
204
- _get_trace_fixture_by_name(trace_dataset_name)
205
- )
320
+ fixture_spans, fixture_evals = reset_fixture_span_ids_and_timestamps(
321
+ (
322
+ # Apply `encode` here because legacy jsonl files contains UUIDs as strings.
323
+ # `encode` removes the hyphens in the UUIDs.
324
+ decode_otlp_span(encode_span_to_otlp(span))
325
+ for span in load_example_traces(trace_dataset_name).to_spans()
326
+ ),
327
+ get_evals_from_fixture(trace_dataset_name),
206
328
  )
207
- Thread(
208
- target=_load_items,
209
- args=(traces, fixture_spans, simulate_streaming),
210
- daemon=True,
211
- ).start()
212
- fixture_evals = list(get_evals_from_fixture(trace_dataset_name))
213
- Thread(
214
- target=_load_items,
215
- args=(traces, fixture_evals, simulate_streaming),
216
- daemon=True,
217
- ).start()
329
+ dataset_fixtures = list(get_dataset_fixtures(trace_dataset_name))
330
+ if not read_only:
331
+ Thread(
332
+ target=send_dataset_fixtures,
333
+ args=(f"http://{host}:{port}", dataset_fixtures),
334
+ ).start()
218
335
  umap_params_list = args.umap_params.split(",")
219
336
  umap_params = UMAPParameters(
220
337
  min_dist=float(umap_params_list[0]),
221
338
  n_neighbors=int(umap_params_list[1]),
222
339
  n_samples=int(umap_params_list[2]),
223
340
  )
224
- read_only = args.read_only
225
- logger.info(f"Server umap params: {umap_params}")
341
+
342
+ if enable_prometheus := get_env_enable_prometheus():
343
+ from phoenix.server.prometheus import start_prometheus
344
+
345
+ start_prometheus()
346
+
347
+ engine = create_engine_and_run_migrations(db_connection_str)
348
+ instrumentation_cleanups = instrument_engine_if_enabled(engine)
349
+ factory = DbSessionFactory(db=_db(engine), dialect=engine.dialect.name)
350
+ corpus_model = (
351
+ None if corpus_inferences is None else create_model_from_inferences(corpus_inferences)
352
+ )
353
+
354
+ # Get enable_websockets from environment variable or command line argument
355
+ enable_websockets = get_env_enable_websockets()
356
+ if args.enable_websockets is not None:
357
+ enable_websockets = args.enable_websockets.lower() == "true"
358
+ if enable_websockets is None:
359
+ enable_websockets = True
360
+
361
+ # Print information about the server
362
+ root_path = urljoin(f"http://{host}:{port}", host_root_path)
363
+ msg = _WELCOME_MESSAGE.render(
364
+ version=phoenix_version,
365
+ ui_path=root_path,
366
+ grpc_path=f"http://{host}:{get_env_grpc_port()}",
367
+ http_path=urljoin(root_path, "v1/traces"),
368
+ storage=get_printable_db_url(db_connection_str),
369
+ schema=get_env_database_schema(),
370
+ auth_enabled=authentication_enabled,
371
+ websockets_enabled=enable_websockets,
372
+ )
373
+ if sys.platform.startswith("win"):
374
+ msg = codecs.encode(msg, "ascii", errors="ignore").decode("ascii").strip()
375
+ scaffolder_config = ScaffolderConfig(
376
+ db=factory,
377
+ tracing_fixture_names=tracing_fixture_names,
378
+ force_fixture_ingestion=force_fixture_ingestion,
379
+ scaffold_datasets=scaffold_datasets,
380
+ phoenix_url=root_path,
381
+ )
382
+ email_sender = None
383
+ if mail_sever := get_env_smtp_hostname():
384
+ assert (mail_username := get_env_smtp_username()), "SMTP username is required"
385
+ assert (mail_password := get_env_smtp_password()), "SMTP password is required"
386
+ assert (sender_email := get_env_smtp_mail_from()), "SMTP mail_from is required"
387
+ email_sender = SimpleEmailSender(
388
+ smtp_server=mail_sever,
389
+ smtp_port=get_env_smtp_port(),
390
+ username=mail_username,
391
+ password=mail_password,
392
+ sender_email=sender_email,
393
+ connection_method="STARTTLS",
394
+ validate_certs=get_env_smtp_validate_certs(),
395
+ )
396
+
226
397
  app = create_app(
398
+ db=factory,
227
399
  export_path=export_path,
228
400
  model=model,
401
+ enable_websockets=enable_websockets,
402
+ authentication_enabled=authentication_enabled,
229
403
  umap_params=umap_params,
230
- traces=traces,
231
- corpus=None if corpus_dataset is None else create_model_from_datasets(corpus_dataset),
404
+ corpus=corpus_model,
232
405
  debug=args.debug,
406
+ dev=args.dev,
407
+ serve_ui=not args.no_ui,
233
408
  read_only=read_only,
234
- span_store=span_store,
409
+ enable_prometheus=enable_prometheus,
410
+ initial_spans=fixture_spans,
411
+ initial_evaluations=fixture_evals,
412
+ startup_callbacks=[lambda: print(msg)],
413
+ shutdown_callbacks=instrumentation_cleanups,
414
+ secret=secret,
415
+ password_reset_token_expiry=get_env_password_reset_token_expiry(),
416
+ access_token_expiry=get_env_access_token_expiry(),
417
+ refresh_token_expiry=get_env_refresh_token_expiry(),
418
+ scaffolder_config=scaffolder_config,
419
+ email_sender=email_sender,
420
+ oauth2_client_configs=get_env_oauth2_settings(),
235
421
  )
236
- host = args.host or get_env_host()
237
- port = args.port or get_env_port()
238
- server = Server(config=Config(app, host=host, port=port))
422
+ server = Server(config=Config(app, host=host, port=port, root_path=host_root_path)) # type: ignore
239
423
  Thread(target=_write_pid_file_when_ready, args=(server,), daemon=True).start()
240
424
 
241
- # Print information about the server
242
- phoenix_version = pkg_resources.get_distribution("arize-phoenix").version
243
- print(
244
- _WELCOME_MESSAGE.format(phoenix_version, host if host != "0.0.0.0" else "localhost", port)
245
- )
246
-
247
425
  # Start the server
248
426
  server.run()
427
+
428
+
429
+ def initialize_settings() -> None:
430
+ Settings.logging_mode = get_env_logging_mode()
431
+ Settings.logging_level = get_env_logging_level()
432
+ Settings.db_logging_level = get_env_db_logging_level()
433
+ Settings.log_migrations = get_env_log_migrations()
434
+
435
+
436
+ if __name__ == "__main__":
437
+ main()
@@ -0,0 +1,52 @@
1
+ from collections.abc import Iterable
2
+ from typing import Any
3
+
4
+ from authlib.integrations.base_client import BaseApp
5
+ from authlib.integrations.base_client.async_app import AsyncOAuth2Mixin
6
+ from authlib.integrations.base_client.async_openid import AsyncOpenIDMixin
7
+ from authlib.integrations.httpx_client import AsyncOAuth2Client as AsyncHttpxOAuth2Client
8
+
9
+ from phoenix.config import OAuth2ClientConfig
10
+
11
+
12
+ class OAuth2Client(AsyncOAuth2Mixin, AsyncOpenIDMixin, BaseApp): # type:ignore[misc]
13
+ """
14
+ An OAuth2 client class that supports OpenID Connect. Adapted from authlib's
15
+ `StarletteOAuth2App` to be useable without integration with Starlette.
16
+
17
+ https://github.com/lepture/authlib/blob/904d66bebd79bf39fb8814353a22bab7d3e092c4/authlib/integrations/starlette_client/apps.py#L58
18
+ """
19
+
20
+ client_cls = AsyncHttpxOAuth2Client
21
+
22
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
23
+ super().__init__(framework=None, *args, **kwargs)
24
+
25
+
26
+ class OAuth2Clients:
27
+ def __init__(self) -> None:
28
+ self._clients: dict[str, OAuth2Client] = {}
29
+
30
+ def add_client(self, config: OAuth2ClientConfig) -> None:
31
+ if (idp_name := config.idp_name) in self._clients:
32
+ raise ValueError(f"oauth client already registered: {idp_name}")
33
+ client = OAuth2Client(
34
+ client_id=config.client_id,
35
+ client_secret=config.client_secret,
36
+ server_metadata_url=config.oidc_config_url,
37
+ client_kwargs={"scope": "openid email profile"},
38
+ )
39
+ assert isinstance(client, OAuth2Client)
40
+ self._clients[config.idp_name] = client
41
+
42
+ def get_client(self, idp_name: str) -> OAuth2Client:
43
+ if (client := self._clients.get(idp_name)) is None:
44
+ raise ValueError(f"unknown or unregistered OAuth2 client: {idp_name}")
45
+ return client
46
+
47
+ @classmethod
48
+ def from_configs(cls, configs: Iterable[OAuth2ClientConfig]) -> "OAuth2Clients":
49
+ oauth2_clients = cls()
50
+ for config in configs:
51
+ oauth2_clients.add_client(config)
52
+ return oauth2_clients
File without changes