interloper-api 0.71.0__tar.gz → 0.72.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.71.0 → interloper_api-0.72.0}/PKG-INFO +1 -1
- {interloper_api-0.71.0 → interloper_api-0.72.0}/pyproject.toml +1 -1
- {interloper_api-0.71.0 → interloper_api-0.72.0}/pyproject.toml.orig +1 -1
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/app.py +21 -1
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/components.py +21 -15
- {interloper_api-0.71.0 → interloper_api-0.72.0}/README.md +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/__init__.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/dependencies/__init__.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/dependencies/auth.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/dependencies/rbac.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/dependencies/state.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/notifications/__init__.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/notifications/invitations.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/__init__.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/admin.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/agent.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/auth.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/backfills.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/catalog.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/health.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/oauth.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/organisations.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/runs.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/tokens.py +0 -0
- {interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/routes/websocket.py +0 -0
|
@@ -10,7 +10,7 @@ from fastapi import APIRouter, FastAPI, Request
|
|
|
10
10
|
from fastapi.middleware.cors import CORSMiddleware
|
|
11
11
|
from fastapi.responses import JSONResponse
|
|
12
12
|
from interloper.catalog.base import Catalog
|
|
13
|
-
from interloper.errors import ComponentDriftError, NotFoundError, QuotaExceededError
|
|
13
|
+
from interloper.errors import ComponentDriftError, HydrationError, NotFoundError, QuotaExceededError
|
|
14
14
|
from interloper_db import Store
|
|
15
15
|
|
|
16
16
|
from interloper_api.dependencies import (
|
|
@@ -87,6 +87,25 @@ async def _component_drift(_request: Request, exception: ComponentDriftError) ->
|
|
|
87
87
|
return JSONResponse(status_code=409, content={"detail": str(exception)})
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
async def _hydration_failed(_request: Request, exception: HydrationError) -> JSONResponse:
|
|
91
|
+
"""Render an unreadable stored record as a conflict rather than a 500.
|
|
92
|
+
|
|
93
|
+
A record that cannot be rebuilt from the database (a payload the active
|
|
94
|
+
``INTERLOPER_ENCRYPTION_KEY`` cannot decrypt, a stored config the class no
|
|
95
|
+
longer accepts) stays broken until an operator re-keys or rewrites it, so
|
|
96
|
+
like drift it surfaces as a 409 carrying the reason instead of an opaque
|
|
97
|
+
500 that only the server log explains.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
_request: The incoming request, unused.
|
|
101
|
+
exception: The raised :class:`HydrationError`.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
A 409 response carrying the exception message as ``detail``.
|
|
105
|
+
"""
|
|
106
|
+
return JSONResponse(status_code=409, content={"detail": str(exception)})
|
|
107
|
+
|
|
108
|
+
|
|
90
109
|
async def _quota_exceeded(_request: Request, exception: QuotaExceededError) -> JSONResponse:
|
|
91
110
|
"""Render store-level quota enforcement as a 429 with structured context.
|
|
92
111
|
|
|
@@ -116,6 +135,7 @@ async def _quota_exceeded(_request: Request, exception: QuotaExceededError) -> J
|
|
|
116
135
|
_ERROR_HANDLERS: dict[type[Exception], Any] = {
|
|
117
136
|
NotFoundError: _not_found,
|
|
118
137
|
ComponentDriftError: _component_drift,
|
|
138
|
+
HydrationError: _hydration_failed,
|
|
119
139
|
QuotaExceededError: _quota_exceeded,
|
|
120
140
|
}
|
|
121
141
|
|
|
@@ -6,8 +6,9 @@ unsaved config — resolving a FetchField's options (``/resolve``) and
|
|
|
6
6
|
checking a connection (``/check``).
|
|
7
7
|
|
|
8
8
|
The response shape is kind-agnostic — identity, drift ``status``, ``config``
|
|
9
|
-
(decoded for secret kinds on detail responses
|
|
10
|
-
typed ``relations``, and one
|
|
9
|
+
(decoded for secret kinds on detail responses; the schema's ``x-public``
|
|
10
|
+
subset elsewhere), machine-owned ``state``, typed ``relations``, and one
|
|
11
|
+
level of ``children`` (a source's assets).
|
|
11
12
|
What a kind's config looks like and which relation types it may declare
|
|
12
13
|
come from the catalog (``/catalog``), not from this router.
|
|
13
14
|
"""
|
|
@@ -120,9 +121,9 @@ class ComponentUpdateRequest(BaseModel):
|
|
|
120
121
|
class ComponentResponse(BaseModel):
|
|
121
122
|
"""Response body for a component of any kind.
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
A secret kind whose payload does not decrypt carries ``status``
|
|
125
|
+
``unreadable`` and no ``config`` at all, rather than a subset that was
|
|
126
|
+
never read.
|
|
126
127
|
"""
|
|
127
128
|
|
|
128
129
|
id: UUID
|
|
@@ -134,7 +135,6 @@ class ComponentResponse(BaseModel):
|
|
|
134
135
|
config: dict[str, Any] | None = None
|
|
135
136
|
state: dict[str, Any] | None = None
|
|
136
137
|
encrypted: bool = False
|
|
137
|
-
auto_renew: bool | None = None
|
|
138
138
|
parent_id: UUID | None = None
|
|
139
139
|
relations: dict[str, list[RelationRef]] = {}
|
|
140
140
|
children: list[ComponentResponse] = []
|
|
@@ -152,9 +152,15 @@ class ComponentResponse(BaseModel):
|
|
|
152
152
|
) -> ComponentResponse:
|
|
153
153
|
"""Convert a component row to its response model.
|
|
154
154
|
|
|
155
|
-
``status`` is the
|
|
156
|
-
|
|
157
|
-
payload as ``config`` only
|
|
155
|
+
``status`` is the usability state hydration gates on: catalog resolution
|
|
156
|
+
(drift detection) plus, for an encrypted row, whether its payload
|
|
157
|
+
decrypts. Secret kinds expose their decoded payload as ``config`` only
|
|
158
|
+
when *include_config* is set (detail responses); otherwise ``config``
|
|
159
|
+
carries just the schema's ``x-public`` subset (operational fields such
|
|
160
|
+
as a connection's ``auto_renew``). An ``unreadable`` row carries no
|
|
161
|
+
``config`` either way: the reason rides its ``status``, so the
|
|
162
|
+
collection still lists and the UI can say what is wrong instead of the
|
|
163
|
+
request failing over one row.
|
|
158
164
|
|
|
159
165
|
Args:
|
|
160
166
|
row: The component row to convert.
|
|
@@ -171,11 +177,12 @@ class ComponentResponse(BaseModel):
|
|
|
171
177
|
|
|
172
178
|
config: dict[str, Any] | None = row.config
|
|
173
179
|
if KINDS[row.kind].sensitive:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
if status is ComponentStatus.UNREADABLE:
|
|
181
|
+
config = None
|
|
182
|
+
elif include_config:
|
|
183
|
+
config = store.components.decode_config(row)
|
|
184
|
+
else:
|
|
185
|
+
config = store.components.public_config(row)
|
|
179
186
|
|
|
180
187
|
return cls(
|
|
181
188
|
id=row.id,
|
|
@@ -187,7 +194,6 @@ class ComponentResponse(BaseModel):
|
|
|
187
194
|
config=config,
|
|
188
195
|
state=row.state,
|
|
189
196
|
encrypted=row.encrypted,
|
|
190
|
-
auto_renew=auto_renew,
|
|
191
197
|
parent_id=row.parent_id,
|
|
192
198
|
relations=_relations_of(row),
|
|
193
199
|
children=[
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/notifications/__init__.py
RENAMED
|
File without changes
|
{interloper_api-0.71.0 → interloper_api-0.72.0}/src/interloper_api/notifications/invitations.py
RENAMED
|
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
|