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.

Files changed (21) hide show
  1. {interloper_api-0.59.0 → interloper_api-0.60.0}/PKG-INFO +1 -1
  2. {interloper_api-0.59.0 → interloper_api-0.60.0}/pyproject.toml +1 -1
  3. {interloper_api-0.59.0 → interloper_api-0.60.0}/pyproject.toml.orig +1 -1
  4. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/admin.py +35 -18
  5. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/backfills.py +8 -11
  6. {interloper_api-0.59.0 → interloper_api-0.60.0}/README.md +0 -0
  7. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/__init__.py +0 -0
  8. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/app.py +0 -0
  9. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/components.py +0 -0
  10. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/dependencies.py +0 -0
  11. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/email.py +0 -0
  12. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/__init__.py +0 -0
  13. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/agent.py +0 -0
  14. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/auth.py +0 -0
  15. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/catalog.py +0 -0
  16. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/components.py +0 -0
  17. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/oauth.py +0 -0
  18. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/organisations.py +0 -0
  19. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/runs.py +0 -0
  20. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/tokens.py +0 -0
  21. {interloper_api-0.59.0 → interloper_api-0.60.0}/src/interloper_api/routes/ws.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: interloper-api
3
- Version: 0.59.0
3
+ Version: 0.60.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.59.0"
3
+ version = "0.60.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.59.0"
6
+ version = "0.60.0"
7
7
  description = "Interloper FastAPI routes"
8
8
  readme = "README.md"
9
9
  authors = [{ name = "Guillaume Onfroy", email = "guillaume@digitlcloud.com" }]
@@ -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
- max_sources=overrides.max_sources if overrides.max_sources is not None else defaults.max_sources,
482
- max_assets_per_source=(
483
- overrides.max_assets_per_source
484
- if overrides.max_assets_per_source is not None
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(period_start=period_start, defaults=defaults, organisations=organisations)
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
- try:
104
- backfill = store.create_backfill(
105
- job.org_id,
106
- component_id=body.component_id,
107
- start_date=body.start_date,
108
- end_date=body.end_date,
109
- concurrency=body.concurrency,
110
- fail_fast=body.fail_fast,
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