interloper-api 0.59.0__tar.gz → 0.60.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.
Potentially problematic release.
This version of interloper-api might be problematic. Click here for more details.
- {interloper_api-0.59.0 → interloper_api-0.60.0}/PKG-INFO +1 -1
- {interloper_api-0.59.0 → interloper_api-0.60.0}/pyproject.toml +1 -1
- {interloper_api-0.59.0 → interloper_api-0.60.0}/pyproject.toml.orig +1 -1
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/admin.py +35 -18
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/backfills.py +8 -11
- {interloper_api-0.59.0 → interloper_api-0.60.0}/README.md +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/__init__.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/app.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/components.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/dependencies.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/email.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/__init__.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/agent.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/auth.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/catalog.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/components.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/oauth.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/organisations.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/runs.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/tokens.py +0 -0
- {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/ws.py +0 -0
|
@@ -18,7 +18,7 @@ from uuid import UUID
|
|
|
18
18
|
|
|
19
19
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
20
20
|
from interloper_db import Organisation, Profile, Store
|
|
21
|
-
from interloper_db.store.quotas import METRIC_SUCCESSFUL_RUNS
|
|
21
|
+
from interloper_db.store.quotas import METRIC_SUCCESSFUL_RUNS, QUOTAS
|
|
22
22
|
from pydantic import BaseModel, Field
|
|
23
23
|
|
|
24
24
|
from interloper_api.dependencies import get_admin_config, get_quota_defaults, get_store, require_super_admin
|
|
@@ -234,6 +234,7 @@ class AdminQuotaLimits(BaseModel):
|
|
|
234
234
|
max_sources: int | None = None
|
|
235
235
|
max_assets_per_source: int | None = None
|
|
236
236
|
max_successful_runs_per_month: int | None = None
|
|
237
|
+
max_backfill_days: int | None = None
|
|
237
238
|
|
|
238
239
|
|
|
239
240
|
class AdminOrgQuotaStatus(BaseModel):
|
|
@@ -254,11 +255,20 @@ class AdminOrgQuotaStatus(BaseModel):
|
|
|
254
255
|
recomputed_successful_runs: int
|
|
255
256
|
|
|
256
257
|
|
|
258
|
+
class AdminQuotaField(BaseModel):
|
|
259
|
+
"""One quota's display descriptor: key, registry label, instance default."""
|
|
260
|
+
|
|
261
|
+
key: str
|
|
262
|
+
label: str
|
|
263
|
+
default: int | None = None
|
|
264
|
+
|
|
265
|
+
|
|
257
266
|
class AdminQuotasResponse(BaseModel):
|
|
258
267
|
"""Quota overview: global defaults plus per-organisation status."""
|
|
259
268
|
|
|
260
269
|
period_start: dt.date
|
|
261
270
|
defaults: AdminQuotaLimits
|
|
271
|
+
fields: list[AdminQuotaField]
|
|
262
272
|
organisations: list[AdminOrgQuotaStatus]
|
|
263
273
|
|
|
264
274
|
|
|
@@ -268,6 +278,7 @@ class AdminQuotaUpdateRequest(BaseModel):
|
|
|
268
278
|
max_sources: int | None = Field(default=None, ge=0)
|
|
269
279
|
max_assets_per_source: int | None = Field(default=None, ge=0)
|
|
270
280
|
max_successful_runs_per_month: int | None = Field(default=None, ge=0)
|
|
281
|
+
max_backfill_days: int | None = Field(default=None, ge=0)
|
|
271
282
|
|
|
272
283
|
|
|
273
284
|
class AdminActivityEntry(BaseModel):
|
|
@@ -468,30 +479,31 @@ def _quota_limits(limits: Any) -> AdminQuotaLimits:
|
|
|
468
479
|
"""Map limits from a ``{key: limit}`` dict (store) or attributes (settings)."""
|
|
469
480
|
if isinstance(limits, dict):
|
|
470
481
|
return AdminQuotaLimits(**{key: limits.get(key) for key in AdminQuotaLimits.model_fields})
|
|
471
|
-
return AdminQuotaLimits(
|
|
472
|
-
max_sources=getattr(limits, "max_sources", None),
|
|
473
|
-
max_assets_per_source=getattr(limits, "max_assets_per_source", None),
|
|
474
|
-
max_successful_runs_per_month=getattr(limits, "max_successful_runs_per_month", None),
|
|
475
|
-
)
|
|
482
|
+
return AdminQuotaLimits(**{key: getattr(limits, key, None) for key in AdminQuotaLimits.model_fields})
|
|
476
483
|
|
|
477
484
|
|
|
478
485
|
def _effective_limits(overrides: AdminQuotaLimits, defaults: AdminQuotaLimits) -> AdminQuotaLimits:
|
|
479
486
|
"""Per-org overrides win field-by-field over the global defaults."""
|
|
480
487
|
return AdminQuotaLimits(
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
else defaults.max_assets_per_source
|
|
486
|
-
),
|
|
487
|
-
max_successful_runs_per_month=(
|
|
488
|
-
overrides.max_successful_runs_per_month
|
|
489
|
-
if overrides.max_successful_runs_per_month is not None
|
|
490
|
-
else defaults.max_successful_runs_per_month
|
|
491
|
-
),
|
|
488
|
+
**{
|
|
489
|
+
key: override if (override := getattr(overrides, key)) is not None else getattr(defaults, key)
|
|
490
|
+
for key in AdminQuotaLimits.model_fields
|
|
491
|
+
}
|
|
492
492
|
)
|
|
493
493
|
|
|
494
494
|
|
|
495
|
+
def _quota_fields(defaults: AdminQuotaLimits) -> list[AdminQuotaField]:
|
|
496
|
+
"""Field descriptors for admin quota surfaces, in the wire model's order.
|
|
497
|
+
|
|
498
|
+
Labels come from the registry definitions, so the frontend renders new
|
|
499
|
+
quotas without a matching code change.
|
|
500
|
+
"""
|
|
501
|
+
return [
|
|
502
|
+
AdminQuotaField(key=key, label=QUOTAS[key].label, default=getattr(defaults, key))
|
|
503
|
+
for key in AdminQuotaLimits.model_fields
|
|
504
|
+
]
|
|
505
|
+
|
|
506
|
+
|
|
495
507
|
def _require_org(store: Store, org_id: UUID) -> Organisation:
|
|
496
508
|
"""Fetch an organisation or raise 404."""
|
|
497
509
|
org = store.get_organisation(org_id)
|
|
@@ -592,7 +604,12 @@ def get_quotas(
|
|
|
592
604
|
recomputed_successful_runs=recomputed.get(org.id, 0),
|
|
593
605
|
)
|
|
594
606
|
)
|
|
595
|
-
return AdminQuotasResponse(
|
|
607
|
+
return AdminQuotasResponse(
|
|
608
|
+
period_start=period_start,
|
|
609
|
+
defaults=defaults,
|
|
610
|
+
fields=_quota_fields(defaults),
|
|
611
|
+
organisations=organisations,
|
|
612
|
+
)
|
|
596
613
|
|
|
597
614
|
|
|
598
615
|
def _activity_title(entry: dict[str, Any]) -> tuple[str, str | None]:
|
|
@@ -100,17 +100,14 @@ def create_backfill(
|
|
|
100
100
|
job = load_authorized(
|
|
101
101
|
lambda i: store.get_component(i, kind="job"), body.component_id, user, store, label="Job", minimum="editor"
|
|
102
102
|
)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
except ValueError as e: # span cap
|
|
113
|
-
raise HTTPException(status_code=400, detail=str(e))
|
|
103
|
+
backfill = store.create_backfill(
|
|
104
|
+
job.org_id,
|
|
105
|
+
component_id=body.component_id,
|
|
106
|
+
start_date=body.start_date,
|
|
107
|
+
end_date=body.end_date,
|
|
108
|
+
concurrency=body.concurrency,
|
|
109
|
+
fail_fast=body.fail_fast,
|
|
110
|
+
)
|
|
114
111
|
return _backfill_to_response(backfill)
|
|
115
112
|
|
|
116
113
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|