interloper-api 0.6.0__tar.gz → 0.7.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.6.0 → interloper_api-0.7.0}/PKG-INFO +1 -1
- {interloper_api-0.6.0 → interloper_api-0.7.0}/pyproject.toml +1 -1
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/runs.py +57 -3
- {interloper_api-0.6.0 → interloper_api-0.7.0}/README.md +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/__init__.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/app.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/dependencies.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/email.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/__init__.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/admin.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/agent.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/assets.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/auth.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/backfills.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/catalog.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/destinations.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/external/__init__.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/external/amazon_ads.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/external/facebook_ads.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/external/google_ads.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/external/pinterest_ads.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/external/snapchat_ads.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/jobs.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/oauth.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/organisations.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/resources.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/sources.py +0 -0
- {interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/ws.py +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import datetime as dt
|
|
6
|
+
from typing import Literal
|
|
6
7
|
from uuid import UUID
|
|
7
8
|
|
|
8
9
|
from fastapi import APIRouter, Depends, HTTPException, Response
|
|
@@ -11,10 +12,15 @@ from interloper_db import Profile, Store
|
|
|
11
12
|
from interloper_db.models import Event, Run
|
|
12
13
|
from pydantic import BaseModel
|
|
13
14
|
|
|
14
|
-
from interloper_api.dependencies import get_org_id, get_store, require_viewer
|
|
15
|
+
from interloper_api.dependencies import get_org_id, get_store, require_editor, require_viewer
|
|
15
16
|
|
|
16
17
|
router = APIRouter()
|
|
17
18
|
|
|
19
|
+
#: Hard cap on the number of events returned in a single page, regardless of
|
|
20
|
+
#: the requested ``limit``. Keeps a pathological ``?limit=1000000`` from loading
|
|
21
|
+
#: an entire run's history into memory at once.
|
|
22
|
+
MAX_EVENTS_PAGE_SIZE = 1000
|
|
23
|
+
|
|
18
24
|
|
|
19
25
|
class RunResponse(BaseModel):
|
|
20
26
|
"""Response body for a run."""
|
|
@@ -25,11 +31,20 @@ class RunResponse(BaseModel):
|
|
|
25
31
|
backfill_id: UUID | None
|
|
26
32
|
partition_date: dt.date | None
|
|
27
33
|
status: str
|
|
34
|
+
retry_of: UUID | None = None
|
|
35
|
+
attempt: int = 1
|
|
36
|
+
retry_scope: str | None = None
|
|
28
37
|
started_at: str | None = None
|
|
29
38
|
completed_at: str | None = None
|
|
30
39
|
created_at: str | None = None
|
|
31
40
|
|
|
32
41
|
|
|
42
|
+
class RetryRequest(BaseModel):
|
|
43
|
+
"""Request body for retrying a failed run."""
|
|
44
|
+
|
|
45
|
+
scope: Literal["all", "failed"] = "all"
|
|
46
|
+
|
|
47
|
+
|
|
33
48
|
class AssetExecutionResponse(BaseModel):
|
|
34
49
|
"""Response body for an asset execution (from the asset_executions view)."""
|
|
35
50
|
|
|
@@ -75,6 +90,9 @@ def _run_to_response(run: Run) -> RunResponse:
|
|
|
75
90
|
backfill_id=run.backfill_id,
|
|
76
91
|
partition_date=run.partition_date,
|
|
77
92
|
status=run.status,
|
|
93
|
+
retry_of=run.retry_of,
|
|
94
|
+
attempt=run.attempt,
|
|
95
|
+
retry_scope=run.retry_scope,
|
|
78
96
|
started_at=str(run.started_at) if run.started_at else None,
|
|
79
97
|
completed_at=str(run.completed_at) if run.completed_at else None,
|
|
80
98
|
created_at=str(run.created_at) if run.created_at else None,
|
|
@@ -172,13 +190,49 @@ def list_asset_executions(
|
|
|
172
190
|
]
|
|
173
191
|
|
|
174
192
|
|
|
193
|
+
@router.post("/{run_id}/retry")
|
|
194
|
+
def retry_run(
|
|
195
|
+
run_id: UUID,
|
|
196
|
+
body: RetryRequest | None = None,
|
|
197
|
+
user: Profile = Depends(require_editor),
|
|
198
|
+
store: Store = Depends(get_store),
|
|
199
|
+
) -> dict[str, str]:
|
|
200
|
+
"""Queue a retry of a failed run.
|
|
201
|
+
|
|
202
|
+
Creates a new run linked to the original via ``retry_of``. With
|
|
203
|
+
``scope="all"`` the whole DAG re-runs; with ``scope="failed"`` only the
|
|
204
|
+
previously failed/cancelled assets re-run.
|
|
205
|
+
"""
|
|
206
|
+
scope = body.scope if body else "all"
|
|
207
|
+
try:
|
|
208
|
+
run = store.retry_run(run_id, scope=scope)
|
|
209
|
+
except NotFoundError:
|
|
210
|
+
raise HTTPException(status_code=404, detail=f"Run {run_id} not found")
|
|
211
|
+
except ValueError as e:
|
|
212
|
+
raise HTTPException(status_code=409, detail=str(e))
|
|
213
|
+
return {"status": "queued", "run_id": str(run.id)}
|
|
214
|
+
|
|
215
|
+
|
|
175
216
|
@router.get("/{run_id}/events")
|
|
176
217
|
def list_run_events(
|
|
177
218
|
run_id: UUID,
|
|
219
|
+
response: Response,
|
|
178
220
|
limit: int = 100,
|
|
221
|
+
offset: int = 0,
|
|
179
222
|
user: Profile = Depends(require_viewer),
|
|
180
223
|
store: Store = Depends(get_store),
|
|
181
224
|
) -> list[EventResponse]:
|
|
182
|
-
"""List events for a run.
|
|
183
|
-
|
|
225
|
+
"""List events for a run, oldest first.
|
|
226
|
+
|
|
227
|
+
Events are ordered ``timestamp ASC`` and paged with ``limit``/``offset``.
|
|
228
|
+
The total number of events for the run (ignoring ``limit``/``offset``) is
|
|
229
|
+
returned in the ``X-Total-Count`` response header so clients can page
|
|
230
|
+
through every event — including the terminal/outcome events
|
|
231
|
+
(``asset_completed``, ``asset_failed``, ``run_failed``, …) that sort last.
|
|
232
|
+
"""
|
|
233
|
+
limit = max(1, min(limit, MAX_EVENTS_PAGE_SIZE))
|
|
234
|
+
offset = max(0, offset)
|
|
235
|
+
total = store.count_events(run_id=run_id)
|
|
236
|
+
response.headers["X-Total-Count"] = str(total)
|
|
237
|
+
events = store.list_events(run_id=run_id, limit=limit, offset=offset)
|
|
184
238
|
return [_event_to_response(e) for e in events]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/external/__init__.py
RENAMED
|
File without changes
|
{interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/external/amazon_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/external/facebook_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/external/google_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/external/pinterest_ads.py
RENAMED
|
File without changes
|
{interloper_api-0.6.0 → interloper_api-0.7.0}/src/interloper_api/routes/external/snapchat_ads.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|