arize-phoenix 4.35.2__py3-none-any.whl → 5.0.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 (104) hide show
  1. {arize_phoenix-4.35.2.dist-info → arize_phoenix-5.0.0.dist-info}/METADATA +10 -12
  2. {arize_phoenix-4.35.2.dist-info → arize_phoenix-5.0.0.dist-info}/RECORD +92 -79
  3. phoenix/__init__.py +86 -0
  4. phoenix/auth.py +275 -14
  5. phoenix/config.py +369 -27
  6. phoenix/db/alembic.ini +0 -34
  7. phoenix/db/engines.py +27 -10
  8. phoenix/db/enums.py +20 -0
  9. phoenix/db/facilitator.py +112 -0
  10. phoenix/db/insertion/dataset.py +0 -1
  11. phoenix/db/insertion/types.py +1 -1
  12. phoenix/db/migrate.py +3 -3
  13. phoenix/db/migrations/env.py +0 -7
  14. phoenix/db/migrations/versions/cd164e83824f_users_and_tokens.py +157 -0
  15. phoenix/db/models.py +145 -60
  16. phoenix/experiments/evaluators/code_evaluators.py +9 -3
  17. phoenix/experiments/functions.py +1 -4
  18. phoenix/inferences/fixtures.py +0 -1
  19. phoenix/inferences/inferences.py +0 -1
  20. phoenix/logging/__init__.py +3 -0
  21. phoenix/logging/_config.py +90 -0
  22. phoenix/logging/_filter.py +6 -0
  23. phoenix/logging/_formatter.py +69 -0
  24. phoenix/metrics/__init__.py +0 -1
  25. phoenix/otel/settings.py +4 -4
  26. phoenix/server/api/README.md +28 -0
  27. phoenix/server/api/auth.py +32 -0
  28. phoenix/server/api/context.py +50 -2
  29. phoenix/server/api/dataloaders/__init__.py +4 -0
  30. phoenix/server/api/dataloaders/user_roles.py +30 -0
  31. phoenix/server/api/dataloaders/users.py +33 -0
  32. phoenix/server/api/exceptions.py +7 -0
  33. phoenix/server/api/mutations/__init__.py +0 -2
  34. phoenix/server/api/mutations/api_key_mutations.py +104 -86
  35. phoenix/server/api/mutations/dataset_mutations.py +8 -8
  36. phoenix/server/api/mutations/experiment_mutations.py +2 -2
  37. phoenix/server/api/mutations/export_events_mutations.py +3 -3
  38. phoenix/server/api/mutations/project_mutations.py +3 -3
  39. phoenix/server/api/mutations/span_annotations_mutations.py +4 -4
  40. phoenix/server/api/mutations/trace_annotations_mutations.py +4 -4
  41. phoenix/server/api/mutations/user_mutations.py +282 -42
  42. phoenix/server/api/openapi/schema.py +2 -2
  43. phoenix/server/api/queries.py +48 -39
  44. phoenix/server/api/routers/__init__.py +11 -0
  45. phoenix/server/api/routers/auth.py +284 -0
  46. phoenix/server/api/routers/embeddings.py +26 -0
  47. phoenix/server/api/routers/oauth2.py +456 -0
  48. phoenix/server/api/routers/v1/__init__.py +38 -16
  49. phoenix/server/api/routers/v1/datasets.py +0 -1
  50. phoenix/server/api/types/ApiKey.py +11 -0
  51. phoenix/server/api/types/AuthMethod.py +9 -0
  52. phoenix/server/api/types/User.py +48 -4
  53. phoenix/server/api/types/UserApiKey.py +35 -1
  54. phoenix/server/api/types/UserRole.py +7 -0
  55. phoenix/server/app.py +105 -34
  56. phoenix/server/bearer_auth.py +161 -0
  57. phoenix/server/email/__init__.py +0 -0
  58. phoenix/server/email/sender.py +26 -0
  59. phoenix/server/email/templates/__init__.py +0 -0
  60. phoenix/server/email/templates/password_reset.html +19 -0
  61. phoenix/server/email/types.py +11 -0
  62. phoenix/server/grpc_server.py +6 -0
  63. phoenix/server/jwt_store.py +504 -0
  64. phoenix/server/main.py +61 -30
  65. phoenix/server/oauth2.py +51 -0
  66. phoenix/server/prometheus.py +20 -0
  67. phoenix/server/rate_limiters.py +191 -0
  68. phoenix/server/static/.vite/manifest.json +31 -31
  69. phoenix/server/static/assets/{components-Dte7_KRd.js → components-REunxTt6.js} +348 -286
  70. phoenix/server/static/assets/index-DAPJxlCw.js +101 -0
  71. phoenix/server/static/assets/{pages-CnTvEGEN.js → pages-1VrMk2pW.js} +559 -291
  72. phoenix/server/static/assets/{vendor-BC3OPQuM.js → vendor-B5IC0ivG.js} +5 -5
  73. phoenix/server/static/assets/{vendor-arizeai-NjB3cZzD.js → vendor-arizeai-aFbT4kl1.js} +2 -2
  74. phoenix/server/static/assets/{vendor-codemirror-gE_JCOgX.js → vendor-codemirror-BEGorXSV.js} +1 -1
  75. phoenix/server/static/assets/{vendor-recharts-BXLYwcXF.js → vendor-recharts-6nUU7gU_.js} +1 -1
  76. phoenix/server/telemetry.py +2 -2
  77. phoenix/server/templates/index.html +1 -0
  78. phoenix/server/types.py +157 -1
  79. phoenix/services.py +0 -1
  80. phoenix/session/client.py +7 -3
  81. phoenix/session/evaluation.py +0 -1
  82. phoenix/session/session.py +0 -1
  83. phoenix/settings.py +9 -0
  84. phoenix/trace/exporter.py +0 -1
  85. phoenix/trace/fixtures.py +0 -2
  86. phoenix/utilities/client.py +16 -0
  87. phoenix/utilities/logging.py +9 -1
  88. phoenix/utilities/re.py +3 -3
  89. phoenix/version.py +1 -1
  90. phoenix/db/migrations/future_versions/README.md +0 -4
  91. phoenix/db/migrations/future_versions/cd164e83824f_users_and_tokens.py +0 -293
  92. phoenix/db/migrations/versions/.gitignore +0 -1
  93. phoenix/server/api/mutations/auth.py +0 -18
  94. phoenix/server/api/mutations/auth_mutations.py +0 -65
  95. phoenix/server/static/assets/index-fq1-hCK4.js +0 -100
  96. phoenix/trace/langchain/__init__.py +0 -3
  97. phoenix/trace/langchain/instrumentor.py +0 -35
  98. phoenix/trace/llama_index/__init__.py +0 -3
  99. phoenix/trace/llama_index/callback.py +0 -103
  100. phoenix/trace/openai/__init__.py +0 -3
  101. phoenix/trace/openai/instrumentor.py +0 -31
  102. {arize_phoenix-4.35.2.dist-info → arize_phoenix-5.0.0.dist-info}/WHEEL +0 -0
  103. {arize_phoenix-4.35.2.dist-info → arize_phoenix-5.0.0.dist-info}/licenses/IP_NOTICE +0 -0
  104. {arize_phoenix-4.35.2.dist-info → arize_phoenix-5.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,20 +1,22 @@
1
- from datetime import datetime
2
- from typing import Any, Dict, Optional
1
+ from datetime import datetime, timezone
2
+ from typing import Optional
3
3
 
4
- import jwt
5
4
  import strawberry
6
- from sqlalchemy import insert, select
5
+ from sqlalchemy import select
7
6
  from strawberry import UNSET
8
7
  from strawberry.relay import GlobalID
9
8
  from strawberry.types import Info
10
9
 
11
- from phoenix.db import models
10
+ from phoenix.db import enums, models
11
+ from phoenix.server.api.auth import IsAdmin, IsNotReadOnly
12
12
  from phoenix.server.api.context import Context
13
- from phoenix.server.api.exceptions import NotFound
14
- from phoenix.server.api.mutations.auth import HasSecret, IsAuthenticated
13
+ from phoenix.server.api.exceptions import Unauthorized
15
14
  from phoenix.server.api.queries import Query
16
15
  from phoenix.server.api.types.node import from_global_id_with_expected_type
17
16
  from phoenix.server.api.types.SystemApiKey import SystemApiKey
17
+ from phoenix.server.api.types.UserApiKey import UserApiKey
18
+ from phoenix.server.bearer_auth import PhoenixUser
19
+ from phoenix.server.types import ApiKeyAttributes, ApiKeyClaims, ApiKeyId, UserId
18
20
 
19
21
 
20
22
  @strawberry.type
@@ -31,119 +33,135 @@ class CreateApiKeyInput:
31
33
  expires_at: Optional[datetime] = UNSET
32
34
 
33
35
 
36
+ @strawberry.type
37
+ class CreateUserApiKeyMutationPayload:
38
+ jwt: str
39
+ api_key: UserApiKey
40
+ query: Query
41
+
42
+
43
+ @strawberry.input
44
+ class CreateUserApiKeyInput:
45
+ name: str
46
+ description: Optional[str] = UNSET
47
+ expires_at: Optional[datetime] = UNSET
48
+
49
+
34
50
  @strawberry.input
35
51
  class DeleteApiKeyInput:
36
52
  id: GlobalID
37
53
 
38
54
 
39
55
  @strawberry.type
40
- class DeleteSystemApiKeyMutationPayload:
41
- id: GlobalID
56
+ class DeleteApiKeyMutationPayload:
57
+ apiKeyId: GlobalID
42
58
  query: Query
43
59
 
44
60
 
45
61
  @strawberry.type
46
62
  class ApiKeyMutationMixin:
47
- @strawberry.mutation(permission_classes=[HasSecret, IsAuthenticated]) # type: ignore
63
+ @strawberry.mutation(permission_classes=[IsNotReadOnly, IsAdmin]) # type: ignore
48
64
  async def create_system_api_key(
49
65
  self, info: Info[Context, None], input: CreateApiKeyInput
50
66
  ) -> CreateSystemApiKeyMutationPayload:
51
- # TODO(auth): safe guard against auth being disabled and secret not being set
67
+ assert (token_store := info.context.token_store) is not None
68
+ user_role = enums.UserRole.SYSTEM
52
69
  async with info.context.db() as session:
53
70
  # Get the system user - note this could be pushed into a dataloader
54
71
  system_user = await session.scalar(
55
72
  select(models.User)
56
73
  .join(models.UserRole) # Join User with UserRole
57
- .where(models.UserRole.name == "SYSTEM") # Filter where role is SYSTEM
74
+ .where(models.UserRole.name == user_role.value) # Filter where role is SYSTEM
75
+ .order_by(models.User.id)
58
76
  .limit(1)
59
77
  )
60
78
  if system_user is None:
61
79
  raise ValueError("System user not found")
62
-
63
- insert_stmt = (
64
- insert(models.APIKey)
65
- .values(
66
- user_id=system_user.id,
67
- name=input.name,
68
- description=input.description or None,
69
- expires_at=input.expires_at or None,
70
- )
71
- .returning(models.APIKey)
72
- )
73
- api_key = await session.scalar(insert_stmt)
74
- assert api_key is not None
75
-
76
- encoded_jwt = create_jwt(
77
- secret=info.context.get_secret(),
78
- name=api_key.name,
79
- id=api_key.id,
80
- description=api_key.description,
81
- iat=api_key.created_at,
82
- exp=api_key.expires_at,
80
+ issued_at = datetime.now(timezone.utc)
81
+ claims = ApiKeyClaims(
82
+ subject=UserId(system_user.id),
83
+ issued_at=issued_at,
84
+ expiration_time=input.expires_at or None,
85
+ attributes=ApiKeyAttributes(
86
+ user_role=user_role,
87
+ name=input.name,
88
+ description=input.description,
89
+ ),
83
90
  )
91
+ token, token_id = await token_store.create_api_key(claims)
84
92
  return CreateSystemApiKeyMutationPayload(
85
- jwt=encoded_jwt,
93
+ jwt=token,
86
94
  api_key=SystemApiKey(
87
- id_attr=api_key.id,
88
- name=api_key.name,
89
- description=api_key.description,
90
- created_at=api_key.created_at,
91
- expires_at=api_key.expires_at,
95
+ id_attr=int(token_id),
96
+ name=input.name,
97
+ description=input.description or None,
98
+ created_at=issued_at,
99
+ expires_at=input.expires_at or None,
92
100
  ),
93
101
  query=Query(),
94
102
  )
95
103
 
96
- @strawberry.mutation(permission_classes=[HasSecret, IsAuthenticated]) # type: ignore
104
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
105
+ async def create_user_api_key(
106
+ self, info: Info[Context, None], input: CreateUserApiKeyInput
107
+ ) -> CreateUserApiKeyMutationPayload:
108
+ assert (token_store := info.context.token_store) is not None
109
+ try:
110
+ user = info.context.request.user # type: ignore
111
+ assert isinstance(user, PhoenixUser)
112
+ except AttributeError:
113
+ raise ValueError("User not found")
114
+ issued_at = datetime.now(timezone.utc)
115
+ claims = ApiKeyClaims(
116
+ subject=user.identity,
117
+ issued_at=issued_at,
118
+ expiration_time=input.expires_at or None,
119
+ attributes=ApiKeyAttributes(
120
+ user_role=enums.UserRole.MEMBER,
121
+ name=input.name,
122
+ description=input.description,
123
+ ),
124
+ )
125
+ token, token_id = await token_store.create_api_key(claims)
126
+ return CreateUserApiKeyMutationPayload(
127
+ jwt=token,
128
+ api_key=UserApiKey(
129
+ id_attr=int(token_id),
130
+ name=input.name,
131
+ description=input.description or None,
132
+ created_at=issued_at,
133
+ expires_at=input.expires_at or None,
134
+ user_id=int(user.identity),
135
+ ),
136
+ query=Query(),
137
+ )
138
+
139
+ @strawberry.mutation(permission_classes=[IsNotReadOnly, IsAdmin]) # type: ignore
97
140
  async def delete_system_api_key(
98
141
  self, info: Info[Context, None], input: DeleteApiKeyInput
99
- ) -> DeleteSystemApiKeyMutationPayload:
142
+ ) -> DeleteApiKeyMutationPayload:
143
+ assert (token_store := info.context.token_store) is not None
100
144
  api_key_id = from_global_id_with_expected_type(
101
145
  input.id, expected_type_name=SystemApiKey.__name__
102
146
  )
147
+ await token_store.revoke(ApiKeyId(api_key_id))
148
+ return DeleteApiKeyMutationPayload(apiKeyId=input.id, query=Query())
149
+
150
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
151
+ async def delete_user_api_key(
152
+ self, info: Info[Context, None], input: DeleteApiKeyInput
153
+ ) -> DeleteApiKeyMutationPayload:
154
+ assert (token_store := info.context.token_store) is not None
155
+ api_key_id = from_global_id_with_expected_type(
156
+ input.id, expected_type_name=UserApiKey.__name__
157
+ )
103
158
  async with info.context.db() as session:
104
- api_key = await session.get(models.APIKey, api_key_id)
159
+ api_key = await session.scalar(
160
+ select(models.ApiKey).where(models.ApiKey.id == api_key_id)
161
+ )
105
162
  if api_key is None:
106
- raise NotFound(f"Unknown System API Key: {input.id}")
107
-
108
- await session.delete(api_key)
109
-
110
- return DeleteSystemApiKeyMutationPayload(id=input.id, query=Query())
111
-
112
-
113
- def create_jwt(
114
- *,
115
- secret: str,
116
- algorithm: str = "HS256",
117
- name: str,
118
- description: Optional[str],
119
- iat: datetime,
120
- exp: Optional[datetime],
121
- id: int,
122
- ) -> str:
123
- """Create a signed JSON Web Token for authentication
124
-
125
- Args:
126
- secret (str): the secret to sign with
127
- name (str): name of the key / token
128
- description (Optional[str]): description of the token
129
- iat (datetime): the issued at time
130
- exp (Optional[datetime]): the expiry, if set
131
- id (int): the id of the key
132
- algorithm (str, optional): the algorithm to use. Defaults to "HS256".
133
-
134
- Returns:
135
- str: The encoded JWT
136
- """
137
- payload: Dict[str, Any] = {
138
- "name": name,
139
- "description": description,
140
- "iat": iat.utcnow(),
141
- "id": id,
142
- }
143
- if exp is not None:
144
- payload["exp"] = exp.utcnow()
145
-
146
- # Encode the payload to create the JWT
147
- token = jwt.encode(payload, secret, algorithm=algorithm)
148
-
149
- return token
163
+ raise ValueError(f"API key with id {input.id} not found")
164
+ if int((user := info.context.user).identity) != api_key.user_id and not user.is_admin:
165
+ raise Unauthorized("User not authorized to delete")
166
+ await token_store.revoke(ApiKeyId(api_key_id))
167
+ return DeleteApiKeyMutationPayload(apiKeyId=input.id, query=Query())
@@ -12,6 +12,7 @@ from strawberry.types import Info
12
12
 
13
13
  from phoenix.db import models
14
14
  from phoenix.db.helpers import get_eval_trace_ids_for_datasets, get_project_names_for_datasets
15
+ from phoenix.server.api.auth import IsNotReadOnly
15
16
  from phoenix.server.api.context import Context
16
17
  from phoenix.server.api.exceptions import BadRequest, NotFound
17
18
  from phoenix.server.api.helpers.dataset_helpers import (
@@ -28,7 +29,6 @@ from phoenix.server.api.input_types.PatchDatasetExamplesInput import (
28
29
  PatchDatasetExamplesInput,
29
30
  )
30
31
  from phoenix.server.api.input_types.PatchDatasetInput import PatchDatasetInput
31
- from phoenix.server.api.mutations.auth import IsAuthenticated
32
32
  from phoenix.server.api.types.Dataset import Dataset, to_gql_dataset
33
33
  from phoenix.server.api.types.DatasetExample import DatasetExample
34
34
  from phoenix.server.api.types.node import from_global_id_with_expected_type
@@ -44,7 +44,7 @@ class DatasetMutationPayload:
44
44
 
45
45
  @strawberry.type
46
46
  class DatasetMutationMixin:
47
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
47
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
48
48
  async def create_dataset(
49
49
  self,
50
50
  info: Info[Context, None],
@@ -67,7 +67,7 @@ class DatasetMutationMixin:
67
67
  info.context.event_queue.put(DatasetInsertEvent((dataset.id,)))
68
68
  return DatasetMutationPayload(dataset=to_gql_dataset(dataset))
69
69
 
70
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
70
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
71
71
  async def patch_dataset(
72
72
  self,
73
73
  info: Info[Context, None],
@@ -96,7 +96,7 @@ class DatasetMutationMixin:
96
96
  info.context.event_queue.put(DatasetInsertEvent((dataset.id,)))
97
97
  return DatasetMutationPayload(dataset=to_gql_dataset(dataset))
98
98
 
99
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
99
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
100
100
  async def add_spans_to_dataset(
101
101
  self,
102
102
  info: Info[Context, None],
@@ -225,7 +225,7 @@ class DatasetMutationMixin:
225
225
  info.context.event_queue.put(DatasetInsertEvent((dataset.id,)))
226
226
  return DatasetMutationPayload(dataset=to_gql_dataset(dataset))
227
227
 
228
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
228
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
229
229
  async def add_examples_to_dataset(
230
230
  self, info: Info[Context, None], input: AddExamplesToDatasetInput
231
231
  ) -> DatasetMutationPayload:
@@ -351,7 +351,7 @@ class DatasetMutationMixin:
351
351
  info.context.event_queue.put(DatasetInsertEvent((dataset.id,)))
352
352
  return DatasetMutationPayload(dataset=to_gql_dataset(dataset))
353
353
 
354
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
354
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
355
355
  async def delete_dataset(
356
356
  self,
357
357
  info: Info[Context, None],
@@ -382,7 +382,7 @@ class DatasetMutationMixin:
382
382
  info.context.event_queue.put(DatasetDeleteEvent((dataset.id,)))
383
383
  return DatasetMutationPayload(dataset=to_gql_dataset(dataset))
384
384
 
385
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
385
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
386
386
  async def patch_dataset_examples(
387
387
  self,
388
388
  info: Info[Context, None],
@@ -474,7 +474,7 @@ class DatasetMutationMixin:
474
474
  info.context.event_queue.put(DatasetInsertEvent((dataset.id,)))
475
475
  return DatasetMutationPayload(dataset=to_gql_dataset(dataset))
476
476
 
477
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
477
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
478
478
  async def delete_dataset_examples(
479
479
  self, info: Info[Context, None], input: DeleteDatasetExamplesInput
480
480
  ) -> DatasetMutationPayload:
@@ -8,10 +8,10 @@ from strawberry.types import Info
8
8
 
9
9
  from phoenix.db import models
10
10
  from phoenix.db.helpers import get_eval_trace_ids_for_experiments, get_project_names_for_experiments
11
+ from phoenix.server.api.auth import IsNotReadOnly
11
12
  from phoenix.server.api.context import Context
12
13
  from phoenix.server.api.exceptions import CustomGraphQLError
13
14
  from phoenix.server.api.input_types.DeleteExperimentsInput import DeleteExperimentsInput
14
- from phoenix.server.api.mutations.auth import IsAuthenticated
15
15
  from phoenix.server.api.types.Experiment import Experiment, to_gql_experiment
16
16
  from phoenix.server.api.types.node import from_global_id_with_expected_type
17
17
  from phoenix.server.api.utils import delete_projects, delete_traces
@@ -25,7 +25,7 @@ class ExperimentMutationPayload:
25
25
 
26
26
  @strawberry.type
27
27
  class ExperimentMutationMixin:
28
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
28
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
29
29
  async def delete_experiments(
30
30
  self,
31
31
  info: Info[Context, None],
@@ -8,9 +8,9 @@ from strawberry import ID, UNSET
8
8
  from strawberry.types import Info
9
9
 
10
10
  import phoenix.core.model_schema as ms
11
+ from phoenix.server.api.auth import IsNotReadOnly
11
12
  from phoenix.server.api.context import Context
12
13
  from phoenix.server.api.input_types.ClusterInput import ClusterInput
13
- from phoenix.server.api.mutations.auth import IsAuthenticated
14
14
  from phoenix.server.api.types.Event import parse_event_ids_by_inferences_role, unpack_event_id
15
15
  from phoenix.server.api.types.ExportedFile import ExportedFile
16
16
  from phoenix.server.api.types.InferencesRole import AncillaryInferencesRole, InferencesRole
@@ -19,7 +19,7 @@ from phoenix.server.api.types.InferencesRole import AncillaryInferencesRole, Inf
19
19
  @strawberry.type
20
20
  class ExportEventsMutationMixin:
21
21
  @strawberry.mutation(
22
- permission_classes=[IsAuthenticated],
22
+ permission_classes=[IsNotReadOnly],
23
23
  description=(
24
24
  "Given a list of event ids, export the corresponding data subset in Parquet format."
25
25
  " File name is optional, but if specified, should be without file extension. By default"
@@ -51,7 +51,7 @@ class ExportEventsMutationMixin:
51
51
  return ExportedFile(file_name=file_name)
52
52
 
53
53
  @strawberry.mutation(
54
- permission_classes=[IsAuthenticated],
54
+ permission_classes=[IsNotReadOnly],
55
55
  description=(
56
56
  "Given a list of clusters, export the corresponding data subset in Parquet format."
57
57
  " File name is optional, but if specified, should be without file extension. By default"
@@ -6,9 +6,9 @@ from strawberry.types import Info
6
6
 
7
7
  from phoenix.config import DEFAULT_PROJECT_NAME
8
8
  from phoenix.db import models
9
+ from phoenix.server.api.auth import IsNotReadOnly
9
10
  from phoenix.server.api.context import Context
10
11
  from phoenix.server.api.input_types.ClearProjectInput import ClearProjectInput
11
- from phoenix.server.api.mutations.auth import IsAuthenticated
12
12
  from phoenix.server.api.queries import Query
13
13
  from phoenix.server.api.types.node import from_global_id_with_expected_type
14
14
  from phoenix.server.dml_event import ProjectDeleteEvent, SpanDeleteEvent
@@ -16,7 +16,7 @@ from phoenix.server.dml_event import ProjectDeleteEvent, SpanDeleteEvent
16
16
 
17
17
  @strawberry.type
18
18
  class ProjectMutationMixin:
19
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
19
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
20
20
  async def delete_project(self, info: Info[Context, None], id: GlobalID) -> Query:
21
21
  project_id = from_global_id_with_expected_type(global_id=id, expected_type_name="Project")
22
22
  async with info.context.db() as session:
@@ -33,7 +33,7 @@ class ProjectMutationMixin:
33
33
  info.context.event_queue.put(ProjectDeleteEvent((project_id,)))
34
34
  return Query()
35
35
 
36
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
36
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
37
37
  async def clear_project(self, info: Info[Context, None], input: ClearProjectInput) -> Query:
38
38
  project_id = from_global_id_with_expected_type(
39
39
  global_id=input.id, expected_type_name="Project"
@@ -6,11 +6,11 @@ from strawberry import UNSET
6
6
  from strawberry.types import Info
7
7
 
8
8
  from phoenix.db import models
9
+ from phoenix.server.api.auth import IsNotReadOnly
9
10
  from phoenix.server.api.context import Context
10
11
  from phoenix.server.api.input_types.CreateSpanAnnotationInput import CreateSpanAnnotationInput
11
12
  from phoenix.server.api.input_types.DeleteAnnotationsInput import DeleteAnnotationsInput
12
13
  from phoenix.server.api.input_types.PatchAnnotationInput import PatchAnnotationInput
13
- from phoenix.server.api.mutations.auth import IsAuthenticated
14
14
  from phoenix.server.api.queries import Query
15
15
  from phoenix.server.api.types.node import from_global_id_with_expected_type
16
16
  from phoenix.server.api.types.SpanAnnotation import SpanAnnotation, to_gql_span_annotation
@@ -25,7 +25,7 @@ class SpanAnnotationMutationPayload:
25
25
 
26
26
  @strawberry.type
27
27
  class SpanAnnotationMutationMixin:
28
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
28
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
29
29
  async def create_span_annotations(
30
30
  self, info: Info[Context, None], input: List[CreateSpanAnnotationInput]
31
31
  ) -> SpanAnnotationMutationPayload:
@@ -59,7 +59,7 @@ class SpanAnnotationMutationMixin:
59
59
  query=Query(),
60
60
  )
61
61
 
62
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
62
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
63
63
  async def patch_span_annotations(
64
64
  self, info: Info[Context, None], input: List[PatchAnnotationInput]
65
65
  ) -> SpanAnnotationMutationPayload:
@@ -99,7 +99,7 @@ class SpanAnnotationMutationMixin:
99
99
  info.context.event_queue.put(SpanAnnotationInsertEvent((span_annotation.id,)))
100
100
  return SpanAnnotationMutationPayload(span_annotations=patched_annotations, query=Query())
101
101
 
102
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
102
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
103
103
  async def delete_span_annotations(
104
104
  self, info: Info[Context, None], input: DeleteAnnotationsInput
105
105
  ) -> SpanAnnotationMutationPayload:
@@ -6,11 +6,11 @@ from strawberry import UNSET
6
6
  from strawberry.types import Info
7
7
 
8
8
  from phoenix.db import models
9
+ from phoenix.server.api.auth import IsNotReadOnly
9
10
  from phoenix.server.api.context import Context
10
11
  from phoenix.server.api.input_types.CreateTraceAnnotationInput import CreateTraceAnnotationInput
11
12
  from phoenix.server.api.input_types.DeleteAnnotationsInput import DeleteAnnotationsInput
12
13
  from phoenix.server.api.input_types.PatchAnnotationInput import PatchAnnotationInput
13
- from phoenix.server.api.mutations.auth import IsAuthenticated
14
14
  from phoenix.server.api.queries import Query
15
15
  from phoenix.server.api.types.node import from_global_id_with_expected_type
16
16
  from phoenix.server.api.types.TraceAnnotation import TraceAnnotation, to_gql_trace_annotation
@@ -25,7 +25,7 @@ class TraceAnnotationMutationPayload:
25
25
 
26
26
  @strawberry.type
27
27
  class TraceAnnotationMutationMixin:
28
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
28
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
29
29
  async def create_trace_annotations(
30
30
  self, info: Info[Context, None], input: List[CreateTraceAnnotationInput]
31
31
  ) -> TraceAnnotationMutationPayload:
@@ -59,7 +59,7 @@ class TraceAnnotationMutationMixin:
59
59
  query=Query(),
60
60
  )
61
61
 
62
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
62
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
63
63
  async def patch_trace_annotations(
64
64
  self, info: Info[Context, None], input: List[PatchAnnotationInput]
65
65
  ) -> TraceAnnotationMutationPayload:
@@ -98,7 +98,7 @@ class TraceAnnotationMutationMixin:
98
98
  info.context.event_queue.put(TraceAnnotationInsertEvent((trace_annotation.id,)))
99
99
  return TraceAnnotationMutationPayload(trace_annotations=patched_annotations, query=Query())
100
100
 
101
- @strawberry.mutation(permission_classes=[IsAuthenticated]) # type: ignore
101
+ @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore
102
102
  async def delete_trace_annotations(
103
103
  self, info: Info[Context, None], input: DeleteAnnotationsInput
104
104
  ) -> TraceAnnotationMutationPayload: