interloper-api 0.87.0__tar.gz → 0.89.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.
Files changed (25) hide show
  1. {interloper_api-0.87.0 → interloper_api-0.89.0}/PKG-INFO +1 -1
  2. {interloper_api-0.87.0 → interloper_api-0.89.0}/pyproject.toml +1 -1
  3. {interloper_api-0.87.0 → interloper_api-0.89.0}/pyproject.toml.orig +1 -1
  4. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/admin.py +2 -0
  5. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/agent.py +3 -3
  6. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/backfills.py +15 -9
  7. {interloper_api-0.87.0 → interloper_api-0.89.0}/README.md +0 -0
  8. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/__init__.py +0 -0
  9. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/app.py +0 -0
  10. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/dependencies/__init__.py +0 -0
  11. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/dependencies/auth.py +0 -0
  12. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/dependencies/rbac.py +0 -0
  13. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/dependencies/state.py +0 -0
  14. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/notifications/__init__.py +0 -0
  15. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/notifications/invitations.py +0 -0
  16. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/__init__.py +0 -0
  17. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/auth.py +0 -0
  18. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/catalog.py +0 -0
  19. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/components.py +0 -0
  20. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/health.py +0 -0
  21. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/oauth.py +0 -0
  22. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/organisations.py +0 -0
  23. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/runs.py +0 -0
  24. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/tokens.py +0 -0
  25. {interloper_api-0.87.0 → interloper_api-0.89.0}/src/interloper_api/routes/websocket.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: interloper-api
3
- Version: 0.87.0
3
+ Version: 0.89.0
4
4
  Summary: Interloper FastAPI routes
5
5
  Author: Guillaume Onfroy
6
6
  Author-email: Guillaume Onfroy <guillaume@digitlcloud.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "interloper-api"
3
- version = "0.87.0"
3
+ version = "0.89.0"
4
4
  description = "Interloper FastAPI routes"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -3,7 +3,7 @@
3
3
  # ###############
4
4
  [project]
5
5
  name = "interloper-api"
6
- version = "0.87.0"
6
+ version = "0.89.0"
7
7
  description = "Interloper FastAPI routes"
8
8
  readme = "README.md"
9
9
  authors = [{ name = "Guillaume Onfroy", email = "guillaume@digitlcloud.com" }]
@@ -224,6 +224,7 @@ class AdminServicesConfig(BaseModel):
224
224
  reaper: AdminReaperConfig
225
225
  smtp: AdminSmtpConfig
226
226
  telemetry: AdminTelemetryConfig
227
+ app_external_url: str
227
228
  mcp_external_url: str
228
229
 
229
230
 
@@ -419,6 +420,7 @@ class AdminConfigResponse(BaseModel):
419
420
  metrics=settings.otel.metrics,
420
421
  sample_ratio=settings.otel.sample_ratio,
421
422
  ),
423
+ app_external_url=settings.server.external_url,
422
424
  mcp_external_url=settings.mcp.external_url,
423
425
  ),
424
426
  data=AdminDataConfig(
@@ -140,8 +140,8 @@ async def create_session(
140
140
  ) -> SessionResponse:
141
141
  """Create a new agent chat session.
142
142
 
143
- The authenticated user's ``org_id`` is injected into the ADK session
144
- state so agent tools are scoped to the correct organisation.
143
+ The active ``org_id`` and the user's role in it are stamped into the ADK
144
+ session state: the org scopes every tool, the role gates the writes.
145
145
 
146
146
  Args:
147
147
  user: The authenticated user, required to hold at least the ``editor`` role.
@@ -157,7 +157,7 @@ async def create_session(
157
157
  session = await session_service.create_session(
158
158
  app_name=APP_NAME,
159
159
  user_id=str(user.id),
160
- state={"org_id": str(org_id)},
160
+ state={"org_id": str(org_id), "role": store.organisations.member_role(user.id, org_id)},
161
161
  )
162
162
  return SessionResponse.from_session(session)
163
163
 
@@ -7,7 +7,7 @@ from uuid import UUID
7
7
  from fastapi import APIRouter, HTTPException
8
8
  from interloper.errors import NotFoundError
9
9
  from interloper_db.models import Backfill
10
- from pydantic import BaseModel
10
+ from pydantic import BaseModel, Field
11
11
 
12
12
  from interloper_api.dependencies import (
13
13
  CurrentUserDep,
@@ -40,6 +40,10 @@ class BackfillResponse(BaseModel):
40
40
  The target component's identity is resolved server-side, same contract
41
41
  as ``RunResponse``: the ``component_*`` identity fields are ``None``
42
42
  exactly when the target was deleted.
43
+
44
+ ``run_counts`` is the backfill's partitions per status, each read as its
45
+ stack's latest attempt, so a listing can draw the backfill's progress
46
+ without fetching its runs.
43
47
  """
44
48
 
45
49
  id: UUID
@@ -57,13 +61,16 @@ class BackfillResponse(BaseModel):
57
61
  started_at: str | None = None
58
62
  completed_at: str | None = None
59
63
  created_at: str | None = None
64
+ run_counts: dict[str, int] = Field(default_factory=dict)
60
65
 
61
66
  @classmethod
62
- def from_backfill(cls, backfill: Backfill) -> BackfillResponse:
67
+ def from_backfill(cls, backfill: Backfill, run_counts: dict[str, int] | None = None) -> BackfillResponse:
63
68
  """Convert a DB Backfill to a BackfillResponse.
64
69
 
65
70
  Args:
66
71
  backfill: The DB Backfill row, with its ``target`` relationship loaded.
72
+ run_counts: The backfill's partition count per status; None reads
73
+ as no runs yet.
67
74
 
68
75
  Returns:
69
76
  The response model.
@@ -84,6 +91,7 @@ class BackfillResponse(BaseModel):
84
91
  started_at=str(backfill.started_at) if backfill.started_at else None,
85
92
  completed_at=str(backfill.completed_at) if backfill.completed_at else None,
86
93
  created_at=str(backfill.created_at) if backfill.created_at else None,
94
+ run_counts=run_counts or {},
87
95
  )
88
96
 
89
97
 
@@ -112,11 +120,9 @@ def list_backfills(
112
120
  Returns:
113
121
  The organisation's backfills, as response models.
114
122
  """
115
- if active_only:
116
- backfills = store.runs.list_active_backfills(org_id)
117
- else:
118
- backfills = store.runs.list_backfills(org_id)
119
- return [BackfillResponse.from_backfill(b) for b in backfills]
123
+ backfills = store.runs.list_backfills(org_id, active_only=active_only)
124
+ counts = store.runs.count_backfill_runs([backfill.id for backfill in backfills])
125
+ return [BackfillResponse.from_backfill(backfill, counts.get(backfill.id)) for backfill in backfills]
120
126
 
121
127
 
122
128
  @router.post("/", status_code=201)
@@ -153,7 +159,7 @@ def create_backfill(
153
159
  )
154
160
  except ValueError as e:
155
161
  raise HTTPException(status_code=400, detail=str(e))
156
- return BackfillResponse.from_backfill(backfill)
162
+ return BackfillResponse.from_backfill(backfill, store.runs.count_backfill_runs([backfill.id]).get(backfill.id))
157
163
 
158
164
 
159
165
  @router.get("/{backfill_id}")
@@ -180,7 +186,7 @@ def get_backfill(
180
186
  except NotFoundError:
181
187
  raise HTTPException(status_code=404, detail=f"Backfill {backfill_id} not found")
182
188
  authorize_org_member(user, backfill.org_id, store, detail=f"Backfill {backfill_id} not found")
183
- return BackfillResponse.from_backfill(backfill)
189
+ return BackfillResponse.from_backfill(backfill, store.runs.count_backfill_runs([backfill_id]).get(backfill_id))
184
190
 
185
191
 
186
192
  @router.post("/{backfill_id}/cancel")