interloper-api 0.70.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.70.0 → interloper_api-0.72.0}/PKG-INFO +1 -1
- {interloper_api-0.70.0 → interloper_api-0.72.0}/pyproject.toml +1 -1
- {interloper_api-0.70.0 → interloper_api-0.72.0}/pyproject.toml.orig +1 -1
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/app.py +21 -1
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/routes/components.py +24 -7
- {interloper_api-0.70.0 → interloper_api-0.72.0}/README.md +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/__init__.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/dependencies/__init__.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/dependencies/auth.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/dependencies/rbac.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/dependencies/state.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/notifications/__init__.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/notifications/invitations.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/routes/__init__.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/routes/admin.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/routes/agent.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/routes/auth.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/routes/backfills.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/routes/catalog.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/routes/health.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/routes/oauth.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/routes/organisations.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/routes/runs.py +0 -0
- {interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/routes/tokens.py +0 -0
- {interloper_api-0.70.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
|
"""
|
|
@@ -118,7 +119,12 @@ class ComponentUpdateRequest(BaseModel):
|
|
|
118
119
|
|
|
119
120
|
|
|
120
121
|
class ComponentResponse(BaseModel):
|
|
121
|
-
"""Response body for a component of any kind.
|
|
122
|
+
"""Response body for a component of any kind.
|
|
123
|
+
|
|
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.
|
|
127
|
+
"""
|
|
122
128
|
|
|
123
129
|
id: UUID
|
|
124
130
|
org_id: UUID
|
|
@@ -146,9 +152,15 @@ class ComponentResponse(BaseModel):
|
|
|
146
152
|
) -> ComponentResponse:
|
|
147
153
|
"""Convert a component row to its response model.
|
|
148
154
|
|
|
149
|
-
``status`` is the
|
|
150
|
-
|
|
151
|
-
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.
|
|
152
164
|
|
|
153
165
|
Args:
|
|
154
166
|
row: The component row to convert.
|
|
@@ -165,7 +177,12 @@ class ComponentResponse(BaseModel):
|
|
|
165
177
|
|
|
166
178
|
config: dict[str, Any] | None = row.config
|
|
167
179
|
if KINDS[row.kind].sensitive:
|
|
168
|
-
|
|
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)
|
|
169
186
|
|
|
170
187
|
return cls(
|
|
171
188
|
id=row.id,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interloper_api-0.70.0 → interloper_api-0.72.0}/src/interloper_api/notifications/__init__.py
RENAMED
|
File without changes
|
{interloper_api-0.70.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
|