interloper-api 0.76.0__tar.gz → 0.78.0__tar.gz
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.
- {interloper_api-0.76.0 → interloper_api-0.78.0}/PKG-INFO +1 -1
- {interloper_api-0.76.0 → interloper_api-0.78.0}/pyproject.toml +4 -2
- {interloper_api-0.76.0 → interloper_api-0.78.0}/pyproject.toml.orig +3 -3
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/dependencies/__init__.py +22 -0
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/dependencies/auth.py +7 -0
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/dependencies/rbac.py +9 -1
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/dependencies/state.py +11 -1
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/admin.py +42 -38
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/agent.py +19 -14
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/auth.py +24 -19
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/backfills.py +14 -15
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/catalog.py +7 -8
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/components.py +97 -87
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/oauth.py +3 -4
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/organisations.py +32 -27
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/runs.py +38 -19
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/tokens.py +15 -11
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/websocket.py +2 -1
- {interloper_api-0.76.0 → interloper_api-0.78.0}/README.md +0 -0
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/__init__.py +0 -0
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/app.py +0 -0
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/notifications/__init__.py +0 -0
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/notifications/invitations.py +0 -0
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/__init__.py +0 -0
- {interloper_api-0.76.0 → interloper_api-0.78.0}/src/interloper_api/routes/health.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "interloper-api"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.78.0"
|
|
4
4
|
description = "Interloper FastAPI routes"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -27,7 +27,7 @@ otel = [
|
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[build-system]
|
|
30
|
-
requires = ["uv_build>=0.
|
|
30
|
+
requires = ["uv_build>=0.12.9,<0.13"]
|
|
31
31
|
build-backend = "uv_build"
|
|
32
32
|
|
|
33
33
|
[tool.uv.sources.interloper-core]
|
|
@@ -84,4 +84,6 @@ extend-immutable-calls = [
|
|
|
84
84
|
"D103",
|
|
85
85
|
"D104",
|
|
86
86
|
"D107",
|
|
87
|
+
"RUF069",
|
|
88
|
+
"PLW0108",
|
|
87
89
|
]
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# ###############
|
|
4
4
|
[project]
|
|
5
5
|
name = "interloper-api"
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.78.0"
|
|
7
7
|
description = "Interloper FastAPI routes"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
authors = [{ name = "Guillaume Onfroy", email = "guillaume@digitlcloud.com" }]
|
|
@@ -27,7 +27,7 @@ otel = [
|
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[build-system]
|
|
30
|
-
requires = ["uv_build>=0.
|
|
30
|
+
requires = ["uv_build>=0.12.9,<0.13"]
|
|
31
31
|
build-backend = "uv_build"
|
|
32
32
|
|
|
33
33
|
[tool.uv.sources]
|
|
@@ -65,4 +65,4 @@ extend-immutable-calls = [
|
|
|
65
65
|
|
|
66
66
|
[tool.ruff.lint.per-file-ignores]
|
|
67
67
|
"__init__.py" = ["F401", "F403"]
|
|
68
|
-
"tests/**" = ["ANN", "F811", "D101", "D102", "D103", "D104", "D107"]
|
|
68
|
+
"tests/**" = ["ANN", "F811", "D101", "D102", "D103", "D104", "D107", "RUF069", "PLW0108"]
|
|
@@ -6,12 +6,18 @@ organisation per request; ``rbac`` gates handlers on the caller's role.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from interloper_api.dependencies.auth import (
|
|
9
|
+
CurrentUserDep,
|
|
10
|
+
OrgIdDep,
|
|
9
11
|
get_current_org,
|
|
10
12
|
get_current_user,
|
|
11
13
|
get_org_id,
|
|
12
14
|
get_session_context,
|
|
13
15
|
)
|
|
14
16
|
from interloper_api.dependencies.rbac import (
|
|
17
|
+
AdminDep,
|
|
18
|
+
EditorDep,
|
|
19
|
+
SuperAdminDep,
|
|
20
|
+
ViewerDep,
|
|
15
21
|
authorize_org_member,
|
|
16
22
|
load_authorized,
|
|
17
23
|
require_admin,
|
|
@@ -20,6 +26,11 @@ from interloper_api.dependencies.rbac import (
|
|
|
20
26
|
require_viewer,
|
|
21
27
|
)
|
|
22
28
|
from interloper_api.dependencies.state import (
|
|
29
|
+
AdminConfigDep,
|
|
30
|
+
AuthConfigDep,
|
|
31
|
+
CatalogDep,
|
|
32
|
+
QuotaDefaultsDep,
|
|
33
|
+
StoreDep,
|
|
23
34
|
get_admin_config,
|
|
24
35
|
get_auth_config,
|
|
25
36
|
get_catalog,
|
|
@@ -37,6 +48,17 @@ from interloper_api.dependencies.state import (
|
|
|
37
48
|
)
|
|
38
49
|
|
|
39
50
|
__all__ = [
|
|
51
|
+
"AdminConfigDep",
|
|
52
|
+
"AdminDep",
|
|
53
|
+
"AuthConfigDep",
|
|
54
|
+
"CatalogDep",
|
|
55
|
+
"CurrentUserDep",
|
|
56
|
+
"EditorDep",
|
|
57
|
+
"OrgIdDep",
|
|
58
|
+
"QuotaDefaultsDep",
|
|
59
|
+
"StoreDep",
|
|
60
|
+
"SuperAdminDep",
|
|
61
|
+
"ViewerDep",
|
|
40
62
|
"authorize_org_member",
|
|
41
63
|
"get_admin_config",
|
|
42
64
|
"get_auth_config",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from typing import Annotated
|
|
5
6
|
from uuid import UUID
|
|
6
7
|
|
|
7
8
|
from fastapi import Cookie, Depends, HTTPException
|
|
@@ -106,3 +107,9 @@ def get_org_id(
|
|
|
106
107
|
The organisation UUID.
|
|
107
108
|
"""
|
|
108
109
|
return org.id
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# -- Dependency aliases --------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
CurrentUserDep = Annotated[Profile, Depends(get_current_user)]
|
|
115
|
+
OrgIdDep = Annotated[UUID, Depends(get_org_id)]
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from collections.abc import Callable
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Annotated, Any
|
|
7
7
|
from uuid import UUID
|
|
8
8
|
|
|
9
9
|
from fastapi import Depends, HTTPException
|
|
@@ -191,3 +191,11 @@ def require_super_admin(
|
|
|
191
191
|
if not user.is_super_admin:
|
|
192
192
|
raise HTTPException(status_code=403, detail="Requires super-admin privileges")
|
|
193
193
|
return user
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# -- Dependency aliases --------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
ViewerDep = Annotated[Profile, Depends(require_viewer)]
|
|
199
|
+
EditorDep = Annotated[Profile, Depends(require_editor)]
|
|
200
|
+
AdminDep = Annotated[Profile, Depends(require_admin)]
|
|
201
|
+
SuperAdminDep = Annotated[Profile, Depends(require_super_admin)]
|
|
@@ -7,8 +7,9 @@ route reaches for an ambient global of its own.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
from typing import Any
|
|
10
|
+
from typing import Annotated, Any
|
|
11
11
|
|
|
12
|
+
from fastapi import Depends
|
|
12
13
|
from interloper.catalog.base import Catalog
|
|
13
14
|
from interloper_db import Store
|
|
14
15
|
|
|
@@ -169,3 +170,12 @@ def get_quota_defaults() -> Any:
|
|
|
169
170
|
The QuotaSettings instance, or None if not configured.
|
|
170
171
|
"""
|
|
171
172
|
return _quota_defaults
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# -- Dependency aliases --------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
StoreDep = Annotated[Store, Depends(get_store)]
|
|
178
|
+
CatalogDep = Annotated[Catalog, Depends(get_catalog)]
|
|
179
|
+
AuthConfigDep = Annotated[Any, Depends(get_auth_config)]
|
|
180
|
+
AdminConfigDep = Annotated[Any, Depends(get_admin_config)]
|
|
181
|
+
QuotaDefaultsDep = Annotated[Any, Depends(get_quota_defaults)]
|
|
@@ -16,12 +16,16 @@ from importlib import metadata
|
|
|
16
16
|
from typing import Any
|
|
17
17
|
from uuid import UUID
|
|
18
18
|
|
|
19
|
-
from fastapi import APIRouter,
|
|
20
|
-
from interloper_db import Profile, Store
|
|
19
|
+
from fastapi import APIRouter, HTTPException, Request
|
|
21
20
|
from interloper_db.store.quotas import METRIC_SUCCESSFUL_RUNS, QUOTAS
|
|
22
21
|
from pydantic import BaseModel, RootModel, field_validator
|
|
23
22
|
|
|
24
|
-
from interloper_api.dependencies import
|
|
23
|
+
from interloper_api.dependencies import (
|
|
24
|
+
AdminConfigDep,
|
|
25
|
+
QuotaDefaultsDep,
|
|
26
|
+
StoreDep,
|
|
27
|
+
SuperAdminDep,
|
|
28
|
+
)
|
|
25
29
|
from interloper_api.notifications import InvitationEmail
|
|
26
30
|
|
|
27
31
|
logger = logging.getLogger(__name__)
|
|
@@ -647,8 +651,8 @@ def _send_invitation_email(
|
|
|
647
651
|
|
|
648
652
|
@router.get("/config")
|
|
649
653
|
def get_instance_config(
|
|
650
|
-
user:
|
|
651
|
-
config:
|
|
654
|
+
user: SuperAdminDep,
|
|
655
|
+
config: AdminConfigDep,
|
|
652
656
|
) -> AdminConfigResponse:
|
|
653
657
|
"""Read-only snapshot of the instance configuration (secrets redacted).
|
|
654
658
|
|
|
@@ -673,9 +677,9 @@ def get_instance_config(
|
|
|
673
677
|
|
|
674
678
|
@router.get("/quotas")
|
|
675
679
|
def get_quotas(
|
|
676
|
-
user:
|
|
677
|
-
store:
|
|
678
|
-
quota_defaults:
|
|
680
|
+
user: SuperAdminDep,
|
|
681
|
+
store: StoreDep,
|
|
682
|
+
quota_defaults: QuotaDefaultsDep,
|
|
679
683
|
) -> AdminQuotasResponse:
|
|
680
684
|
"""Quota limits and current-period usage for every organisation.
|
|
681
685
|
|
|
@@ -730,8 +734,8 @@ def get_quotas(
|
|
|
730
734
|
def update_org_quota(
|
|
731
735
|
org_id: UUID,
|
|
732
736
|
body: AdminQuotaUpdateRequest,
|
|
733
|
-
user:
|
|
734
|
-
store:
|
|
737
|
+
user: SuperAdminDep,
|
|
738
|
+
store: StoreDep,
|
|
735
739
|
) -> AdminQuotaLimits:
|
|
736
740
|
"""Set an organisation's quota overrides; omitted keys keep their value, null clears one.
|
|
737
741
|
|
|
@@ -784,8 +788,8 @@ def _activity_title(entry: dict[str, Any]) -> tuple[str, str | None]:
|
|
|
784
788
|
@router.get("/organisations/{org_id}/activity")
|
|
785
789
|
def get_organisation_activity(
|
|
786
790
|
org_id: UUID,
|
|
787
|
-
user:
|
|
788
|
-
store:
|
|
791
|
+
user: SuperAdminDep,
|
|
792
|
+
store: StoreDep,
|
|
789
793
|
) -> list[AdminActivityEntry]:
|
|
790
794
|
"""Derived activity feed for one organisation, newest first.
|
|
791
795
|
|
|
@@ -813,8 +817,8 @@ def get_organisation_activity(
|
|
|
813
817
|
|
|
814
818
|
@router.get("/users")
|
|
815
819
|
def list_all_users(
|
|
816
|
-
user:
|
|
817
|
-
store:
|
|
820
|
+
user: SuperAdminDep,
|
|
821
|
+
store: StoreDep,
|
|
818
822
|
) -> list[AdminUserResponse]:
|
|
819
823
|
"""List every user profile with the organisations it belongs to.
|
|
820
824
|
|
|
@@ -842,8 +846,8 @@ def list_all_users(
|
|
|
842
846
|
@router.delete("/users/{user_id}")
|
|
843
847
|
def delete_user(
|
|
844
848
|
user_id: UUID,
|
|
845
|
-
user:
|
|
846
|
-
store:
|
|
849
|
+
user: SuperAdminDep,
|
|
850
|
+
store: StoreDep,
|
|
847
851
|
) -> dict[str, str]:
|
|
848
852
|
"""Delete a user entirely: profile, sessions, tokens, memberships, sent invitations.
|
|
849
853
|
|
|
@@ -869,8 +873,8 @@ def delete_user(
|
|
|
869
873
|
|
|
870
874
|
@router.get("/organisations")
|
|
871
875
|
def list_all_organisations(
|
|
872
|
-
user:
|
|
873
|
-
store:
|
|
876
|
+
user: SuperAdminDep,
|
|
877
|
+
store: StoreDep,
|
|
874
878
|
) -> list[AdminOrganisationResponse]:
|
|
875
879
|
"""List every organisation with its member count, soft-deleted ones included.
|
|
876
880
|
|
|
@@ -896,8 +900,8 @@ def list_all_organisations(
|
|
|
896
900
|
@router.post("/organisations", status_code=201)
|
|
897
901
|
def create_organisation(
|
|
898
902
|
body: CreateOrganisationRequest,
|
|
899
|
-
user:
|
|
900
|
-
store:
|
|
903
|
+
user: SuperAdminDep,
|
|
904
|
+
store: StoreDep,
|
|
901
905
|
) -> AdminOrganisationResponse:
|
|
902
906
|
"""Create an organisation. The super-admin is not added as a member.
|
|
903
907
|
|
|
@@ -922,8 +926,8 @@ def create_organisation(
|
|
|
922
926
|
def update_organisation(
|
|
923
927
|
org_id: UUID,
|
|
924
928
|
body: UpdateOrganisationRequest,
|
|
925
|
-
user:
|
|
926
|
-
store:
|
|
929
|
+
user: SuperAdminDep,
|
|
930
|
+
store: StoreDep,
|
|
927
931
|
) -> AdminOrganisationResponse:
|
|
928
932
|
"""Rename an organisation.
|
|
929
933
|
|
|
@@ -950,8 +954,8 @@ def update_organisation(
|
|
|
950
954
|
def delete_organisation(
|
|
951
955
|
org_id: UUID,
|
|
952
956
|
body: DeleteOrganisationRequest,
|
|
953
|
-
user:
|
|
954
|
-
store:
|
|
957
|
+
user: SuperAdminDep,
|
|
958
|
+
store: StoreDep,
|
|
955
959
|
) -> dict[str, str]:
|
|
956
960
|
"""Soft-delete an organisation: purges its data, keeps execution history and the usage ledger.
|
|
957
961
|
|
|
@@ -983,8 +987,8 @@ def delete_organisation(
|
|
|
983
987
|
@router.get("/organisations/{org_id}/members")
|
|
984
988
|
def list_members(
|
|
985
989
|
org_id: UUID,
|
|
986
|
-
user:
|
|
987
|
-
store:
|
|
990
|
+
user: SuperAdminDep,
|
|
991
|
+
store: StoreDep,
|
|
988
992
|
) -> list[MemberResponse]:
|
|
989
993
|
"""List all members of any organisation.
|
|
990
994
|
|
|
@@ -1014,8 +1018,8 @@ def list_members(
|
|
|
1014
1018
|
def join_organisation(
|
|
1015
1019
|
org_id: UUID,
|
|
1016
1020
|
body: JoinOrganisationRequest,
|
|
1017
|
-
user:
|
|
1018
|
-
store:
|
|
1021
|
+
user: SuperAdminDep,
|
|
1022
|
+
store: StoreDep,
|
|
1019
1023
|
) -> MemberResponse:
|
|
1020
1024
|
"""Add the calling super-admin to any organisation — no invitation needed.
|
|
1021
1025
|
|
|
@@ -1049,8 +1053,8 @@ def update_member_role(
|
|
|
1049
1053
|
org_id: UUID,
|
|
1050
1054
|
user_id: UUID,
|
|
1051
1055
|
body: UpdateRoleRequest,
|
|
1052
|
-
user:
|
|
1053
|
-
store:
|
|
1056
|
+
user: SuperAdminDep,
|
|
1057
|
+
store: StoreDep,
|
|
1054
1058
|
) -> dict[str, str]:
|
|
1055
1059
|
"""Change a member's role in any organisation.
|
|
1056
1060
|
|
|
@@ -1073,8 +1077,8 @@ def update_member_role(
|
|
|
1073
1077
|
def remove_member(
|
|
1074
1078
|
org_id: UUID,
|
|
1075
1079
|
user_id: UUID,
|
|
1076
|
-
user:
|
|
1077
|
-
store:
|
|
1080
|
+
user: SuperAdminDep,
|
|
1081
|
+
store: StoreDep,
|
|
1078
1082
|
) -> dict[str, str]:
|
|
1079
1083
|
"""Remove a member from any organisation.
|
|
1080
1084
|
|
|
@@ -1097,8 +1101,8 @@ def remove_member(
|
|
|
1097
1101
|
@router.get("/organisations/{org_id}/invitations")
|
|
1098
1102
|
def list_invitations(
|
|
1099
1103
|
org_id: UUID,
|
|
1100
|
-
user:
|
|
1101
|
-
store:
|
|
1104
|
+
user: SuperAdminDep,
|
|
1105
|
+
store: StoreDep,
|
|
1102
1106
|
) -> list[InvitationResponse]:
|
|
1103
1107
|
"""List pending invitations for any organisation.
|
|
1104
1108
|
|
|
@@ -1128,8 +1132,8 @@ def invite_member(
|
|
|
1128
1132
|
org_id: UUID,
|
|
1129
1133
|
body: InviteRequest,
|
|
1130
1134
|
request: Request,
|
|
1131
|
-
user:
|
|
1132
|
-
store:
|
|
1135
|
+
user: SuperAdminDep,
|
|
1136
|
+
store: StoreDep,
|
|
1133
1137
|
) -> InvitationResponse:
|
|
1134
1138
|
"""Invite a user to any organisation by email.
|
|
1135
1139
|
|
|
@@ -1171,8 +1175,8 @@ def invite_member(
|
|
|
1171
1175
|
def cancel_invitation(
|
|
1172
1176
|
org_id: UUID,
|
|
1173
1177
|
invitation_id: UUID,
|
|
1174
|
-
user:
|
|
1175
|
-
store:
|
|
1178
|
+
user: SuperAdminDep,
|
|
1179
|
+
store: StoreDep,
|
|
1176
1180
|
) -> dict[str, str]:
|
|
1177
1181
|
"""Cancel a pending invitation in any organisation.
|
|
1178
1182
|
|
|
@@ -12,9 +12,8 @@ from __future__ import annotations
|
|
|
12
12
|
import logging
|
|
13
13
|
from collections.abc import AsyncIterator
|
|
14
14
|
from typing import Any
|
|
15
|
-
from uuid import UUID
|
|
16
15
|
|
|
17
|
-
from fastapi import APIRouter,
|
|
16
|
+
from fastapi import APIRouter, HTTPException, Response
|
|
18
17
|
from fastapi.responses import StreamingResponse
|
|
19
18
|
from google.adk.apps import App
|
|
20
19
|
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
|
|
@@ -22,10 +21,16 @@ from google.adk.runners import Runner
|
|
|
22
21
|
from google.adk.sessions.in_memory_session_service import InMemorySessionService
|
|
23
22
|
from google.genai import types
|
|
24
23
|
from interloper.catalog.base import Catalog
|
|
25
|
-
from interloper_db import
|
|
24
|
+
from interloper_db import Store
|
|
26
25
|
from pydantic import BaseModel
|
|
27
26
|
|
|
28
|
-
from interloper_api.dependencies import
|
|
27
|
+
from interloper_api.dependencies import (
|
|
28
|
+
CatalogDep,
|
|
29
|
+
EditorDep,
|
|
30
|
+
OrgIdDep,
|
|
31
|
+
StoreDep,
|
|
32
|
+
ViewerDep,
|
|
33
|
+
)
|
|
29
34
|
|
|
30
35
|
logger = logging.getLogger(__name__)
|
|
31
36
|
|
|
@@ -128,10 +133,10 @@ class SessionResponse(BaseModel):
|
|
|
128
133
|
|
|
129
134
|
@router.post("/sessions")
|
|
130
135
|
async def create_session(
|
|
131
|
-
user:
|
|
132
|
-
org_id:
|
|
133
|
-
store:
|
|
134
|
-
catalog:
|
|
136
|
+
user: EditorDep,
|
|
137
|
+
org_id: OrgIdDep,
|
|
138
|
+
store: StoreDep,
|
|
139
|
+
catalog: CatalogDep,
|
|
135
140
|
) -> SessionResponse:
|
|
136
141
|
"""Create a new agent chat session.
|
|
137
142
|
|
|
@@ -159,7 +164,7 @@ async def create_session(
|
|
|
159
164
|
|
|
160
165
|
@router.get("/sessions")
|
|
161
166
|
async def list_sessions(
|
|
162
|
-
user:
|
|
167
|
+
user: ViewerDep,
|
|
163
168
|
) -> list[SessionResponse]:
|
|
164
169
|
"""List all agent sessions for the current user.
|
|
165
170
|
|
|
@@ -181,7 +186,7 @@ async def list_sessions(
|
|
|
181
186
|
@router.get("/sessions/{session_id}")
|
|
182
187
|
async def get_session(
|
|
183
188
|
session_id: str,
|
|
184
|
-
user:
|
|
189
|
+
user: ViewerDep,
|
|
185
190
|
) -> Response:
|
|
186
191
|
"""Get a session with its full event history.
|
|
187
192
|
|
|
@@ -214,7 +219,7 @@ async def get_session(
|
|
|
214
219
|
@router.delete("/sessions/{session_id}")
|
|
215
220
|
async def delete_session(
|
|
216
221
|
session_id: str,
|
|
217
|
-
user:
|
|
222
|
+
user: EditorDep,
|
|
218
223
|
) -> dict[str, str]:
|
|
219
224
|
"""Delete an agent session.
|
|
220
225
|
|
|
@@ -248,9 +253,9 @@ async def delete_session(
|
|
|
248
253
|
async def chat(
|
|
249
254
|
session_id: str,
|
|
250
255
|
body: ChatRequest,
|
|
251
|
-
user:
|
|
252
|
-
store:
|
|
253
|
-
catalog:
|
|
256
|
+
user: EditorDep,
|
|
257
|
+
store: StoreDep,
|
|
258
|
+
catalog: CatalogDep,
|
|
254
259
|
) -> StreamingResponse:
|
|
255
260
|
"""Send a message and stream the agent's response as SSE.
|
|
256
261
|
|
|
@@ -4,19 +4,24 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from contextlib import suppress
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Annotated, Any
|
|
8
8
|
from urllib.parse import urlencode
|
|
9
9
|
from uuid import UUID
|
|
10
10
|
from zoneinfo import ZoneInfo
|
|
11
11
|
|
|
12
12
|
import httpx
|
|
13
|
-
from fastapi import APIRouter, Cookie,
|
|
13
|
+
from fastapi import APIRouter, Cookie, HTTPException, Response
|
|
14
14
|
from fastapi.responses import RedirectResponse
|
|
15
15
|
from interloper.errors import NotFoundError
|
|
16
|
-
from interloper_db import Organisation,
|
|
16
|
+
from interloper_db import Organisation, Store
|
|
17
17
|
from pydantic import BaseModel, Field
|
|
18
18
|
|
|
19
|
-
from interloper_api.dependencies import
|
|
19
|
+
from interloper_api.dependencies import (
|
|
20
|
+
AuthConfigDep,
|
|
21
|
+
CurrentUserDep,
|
|
22
|
+
StoreDep,
|
|
23
|
+
get_features,
|
|
24
|
+
)
|
|
20
25
|
|
|
21
26
|
GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
|
|
22
27
|
GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
|
|
@@ -83,8 +88,8 @@ class AuthUserResponse(BaseModel):
|
|
|
83
88
|
|
|
84
89
|
@router.get("/google")
|
|
85
90
|
def google_login(
|
|
91
|
+
auth_config: AuthConfigDep,
|
|
86
92
|
redirect: str | None = None,
|
|
87
|
-
auth_config: Any = Depends(get_auth_config),
|
|
88
93
|
) -> RedirectResponse:
|
|
89
94
|
"""Redirect user to Google's OAuth consent screen.
|
|
90
95
|
|
|
@@ -123,9 +128,9 @@ def google_login(
|
|
|
123
128
|
@router.get("/google/callback")
|
|
124
129
|
def google_callback(
|
|
125
130
|
code: str,
|
|
131
|
+
store: StoreDep,
|
|
132
|
+
auth_config: AuthConfigDep,
|
|
126
133
|
state: str | None = None,
|
|
127
|
-
store: Store = Depends(get_store),
|
|
128
|
-
auth_config: Any = Depends(get_auth_config),
|
|
129
134
|
) -> RedirectResponse:
|
|
130
135
|
"""Exchange Google authorization code for tokens, upsert profile, create session.
|
|
131
136
|
|
|
@@ -224,8 +229,8 @@ def google_callback(
|
|
|
224
229
|
@router.post("/logout")
|
|
225
230
|
def logout(
|
|
226
231
|
response: Response,
|
|
227
|
-
user:
|
|
228
|
-
store:
|
|
232
|
+
user: CurrentUserDep,
|
|
233
|
+
store: StoreDep,
|
|
229
234
|
) -> dict[str, str]:
|
|
230
235
|
"""Delete all sessions for the current user and clear the cookie.
|
|
231
236
|
|
|
@@ -247,8 +252,8 @@ def logout(
|
|
|
247
252
|
|
|
248
253
|
@router.get("/me")
|
|
249
254
|
def get_me(
|
|
250
|
-
store:
|
|
251
|
-
session_token: str | None
|
|
255
|
+
store: StoreDep,
|
|
256
|
+
session_token: Annotated[str | None, Cookie()] = None,
|
|
252
257
|
) -> AuthUserResponse:
|
|
253
258
|
"""Return the current user and their active organisation (if any).
|
|
254
259
|
|
|
@@ -328,8 +333,8 @@ class ProfileResponse(BaseModel):
|
|
|
328
333
|
@router.patch("/me")
|
|
329
334
|
def update_me(
|
|
330
335
|
body: UpdateMeRequest,
|
|
331
|
-
user:
|
|
332
|
-
store:
|
|
336
|
+
user: CurrentUserDep,
|
|
337
|
+
store: StoreDep,
|
|
333
338
|
) -> ProfileResponse:
|
|
334
339
|
"""Update the current user's profile (display name, timezone).
|
|
335
340
|
|
|
@@ -366,9 +371,9 @@ class SwitchOrgRequest(BaseModel):
|
|
|
366
371
|
@router.post("/switch-org")
|
|
367
372
|
def switch_org(
|
|
368
373
|
body: SwitchOrgRequest,
|
|
369
|
-
user:
|
|
370
|
-
store:
|
|
371
|
-
session_token: str | None
|
|
374
|
+
user: CurrentUserDep,
|
|
375
|
+
store: StoreDep,
|
|
376
|
+
session_token: Annotated[str | None, Cookie()] = None,
|
|
372
377
|
) -> dict[str, str]:
|
|
373
378
|
"""Switch the session's active organisation. User must be a member.
|
|
374
379
|
|
|
@@ -403,9 +408,9 @@ class AcceptInviteRequest(BaseModel):
|
|
|
403
408
|
@router.post("/accept-invite")
|
|
404
409
|
def accept_invite(
|
|
405
410
|
body: AcceptInviteRequest,
|
|
406
|
-
user:
|
|
407
|
-
store:
|
|
408
|
-
session_token: str | None
|
|
411
|
+
user: CurrentUserDep,
|
|
412
|
+
store: StoreDep,
|
|
413
|
+
session_token: Annotated[str | None, Cookie()] = None,
|
|
409
414
|
) -> dict[str, str]:
|
|
410
415
|
"""Accept an organisation invitation using its token.
|
|
411
416
|
|
|
@@ -4,19 +4,18 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
|
-
from fastapi import APIRouter,
|
|
7
|
+
from fastapi import APIRouter, HTTPException
|
|
8
8
|
from interloper.errors import NotFoundError
|
|
9
|
-
from interloper_db import Profile, Store
|
|
10
9
|
from interloper_db.models import Backfill
|
|
11
10
|
from pydantic import BaseModel
|
|
12
11
|
|
|
13
12
|
from interloper_api.dependencies import (
|
|
13
|
+
CurrentUserDep,
|
|
14
|
+
OrgIdDep,
|
|
15
|
+
StoreDep,
|
|
16
|
+
ViewerDep,
|
|
14
17
|
authorize_org_member,
|
|
15
|
-
get_current_user,
|
|
16
|
-
get_org_id,
|
|
17
|
-
get_store,
|
|
18
18
|
load_authorized,
|
|
19
|
-
require_viewer,
|
|
20
19
|
)
|
|
21
20
|
|
|
22
21
|
router = APIRouter(prefix="/backfills", tags=["backfills"])
|
|
@@ -96,10 +95,10 @@ class BackfillResponse(BaseModel):
|
|
|
96
95
|
|
|
97
96
|
@router.get("/")
|
|
98
97
|
def list_backfills(
|
|
98
|
+
user: ViewerDep,
|
|
99
|
+
org_id: OrgIdDep,
|
|
100
|
+
store: StoreDep,
|
|
99
101
|
active_only: bool = False,
|
|
100
|
-
user: Profile = Depends(require_viewer),
|
|
101
|
-
org_id: UUID = Depends(get_org_id),
|
|
102
|
-
store: Store = Depends(get_store),
|
|
103
102
|
) -> list[BackfillResponse]:
|
|
104
103
|
"""List backfills for the current organisation.
|
|
105
104
|
|
|
@@ -123,8 +122,8 @@ def list_backfills(
|
|
|
123
122
|
@router.post("/", status_code=201)
|
|
124
123
|
def create_backfill(
|
|
125
124
|
body: BackfillCreateRequest,
|
|
126
|
-
user:
|
|
127
|
-
store:
|
|
125
|
+
user: CurrentUserDep,
|
|
126
|
+
store: StoreDep,
|
|
128
127
|
) -> BackfillResponse:
|
|
129
128
|
"""Queue a backfill for a job over a partition-key range.
|
|
130
129
|
|
|
@@ -160,8 +159,8 @@ def create_backfill(
|
|
|
160
159
|
@router.get("/{backfill_id}")
|
|
161
160
|
def get_backfill(
|
|
162
161
|
backfill_id: UUID,
|
|
163
|
-
user:
|
|
164
|
-
store:
|
|
162
|
+
user: CurrentUserDep,
|
|
163
|
+
store: StoreDep,
|
|
165
164
|
) -> BackfillResponse:
|
|
166
165
|
"""Get a single backfill by ID. Authorized by membership in the backfill's org.
|
|
167
166
|
|
|
@@ -187,8 +186,8 @@ def get_backfill(
|
|
|
187
186
|
@router.post("/{backfill_id}/cancel")
|
|
188
187
|
def cancel_backfill(
|
|
189
188
|
backfill_id: UUID,
|
|
190
|
-
user:
|
|
191
|
-
store:
|
|
189
|
+
user: CurrentUserDep,
|
|
190
|
+
store: StoreDep,
|
|
192
191
|
) -> BackfillResponse:
|
|
193
192
|
"""Cancel a backfill: pending and queued runs are canceled, in-flight runs drain.
|
|
194
193
|
|