interloper-api 0.82.0__tar.gz → 0.83.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.82.0 → interloper_api-0.83.0}/PKG-INFO +1 -1
- {interloper_api-0.82.0 → interloper_api-0.83.0}/pyproject.toml +1 -1
- {interloper_api-0.82.0 → interloper_api-0.83.0}/pyproject.toml.orig +1 -1
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/components.py +109 -32
- {interloper_api-0.82.0 → interloper_api-0.83.0}/README.md +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/__init__.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/app.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/dependencies/__init__.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/dependencies/auth.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/dependencies/rbac.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/dependencies/state.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/notifications/__init__.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/notifications/invitations.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/__init__.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/admin.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/agent.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/auth.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/backfills.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/catalog.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/health.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/oauth.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/organisations.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/runs.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/tokens.py +0 -0
- {interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/routes/websocket.py +0 -0
|
@@ -7,8 +7,10 @@ checking a connection (``/check``).
|
|
|
7
7
|
|
|
8
8
|
The response shape is kind-agnostic — identity, drift ``status``, ``config``
|
|
9
9
|
(decoded for secret kinds on detail responses; the schema's ``x-public``
|
|
10
|
-
subset elsewhere), machine-owned ``state``, typed ``relations``, and
|
|
11
|
-
|
|
10
|
+
subset elsewhere), machine-owned ``state``, typed ``relations``, and the
|
|
11
|
+
components a row owns under ``children`` (a source's assets). The owner is
|
|
12
|
+
the unit: an owned component rides inside its owner and never lists on its
|
|
13
|
+
own, the way the catalog reaches an owned definition through its owner.
|
|
12
14
|
What a kind's config looks like and which relation types it may declare
|
|
13
15
|
come from the catalog (``/catalog``), not from this router.
|
|
14
16
|
"""
|
|
@@ -36,7 +38,7 @@ from interloper.errors import (
|
|
|
36
38
|
from interloper.resource.fields import is_fetch_field_provider
|
|
37
39
|
from interloper.utils.concurrency import invoke
|
|
38
40
|
from interloper.utils.imports import import_from_path
|
|
39
|
-
from interloper_db import Component, ComponentStatus, Store
|
|
41
|
+
from interloper_db import Component, ComponentStatus, DeleteImpact, Store
|
|
40
42
|
from pydantic import BaseModel, Field, ValidationError
|
|
41
43
|
|
|
42
44
|
from interloper_api.dependencies import (
|
|
@@ -70,10 +72,16 @@ class RelationCreateRequest(RelationEntry):
|
|
|
70
72
|
|
|
71
73
|
|
|
72
74
|
class RelationRef(BaseModel):
|
|
73
|
-
"""One relation binding in a component response.
|
|
75
|
+
"""One relation binding in a component response, with enough of its target to name it.
|
|
76
|
+
|
|
77
|
+
``dst_key`` and ``dst_name`` ride along so a surface can label what a
|
|
78
|
+
component is bound to without holding that component's own row.
|
|
79
|
+
"""
|
|
74
80
|
|
|
75
81
|
dst_id: UUID
|
|
76
82
|
dst_kind: str
|
|
83
|
+
dst_key: str
|
|
84
|
+
dst_name: str | None = None
|
|
77
85
|
|
|
78
86
|
|
|
79
87
|
class RelationResponse(BaseModel):
|
|
@@ -90,6 +98,37 @@ class RelationResponse(BaseModel):
|
|
|
90
98
|
dst_kind: str
|
|
91
99
|
|
|
92
100
|
|
|
101
|
+
class UsedByRef(BaseModel):
|
|
102
|
+
"""A component bound to something about to be deleted, as the 409 ``used_by`` payload names it."""
|
|
103
|
+
|
|
104
|
+
id: str
|
|
105
|
+
kind: str
|
|
106
|
+
key: str
|
|
107
|
+
name: str | None = None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class DeleteImpactResponse(BaseModel):
|
|
111
|
+
"""The preview behind a delete confirmation: who blocks it, who merely detaches."""
|
|
112
|
+
|
|
113
|
+
blocking: list[UsedByRef]
|
|
114
|
+
detaching: list[UsedByRef]
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def from_impact(cls, impact: DeleteImpact) -> DeleteImpactResponse:
|
|
118
|
+
"""Convert the store's preview to its response model.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
impact: The store's blocking and detaching referrers.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
The response model.
|
|
125
|
+
"""
|
|
126
|
+
return cls(
|
|
127
|
+
blocking=[UsedByRef.model_validate(ref) for ref in impact.blocking],
|
|
128
|
+
detaching=[UsedByRef.model_validate(ref) for ref in impact.detaching],
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
93
132
|
class ComponentCreateRequest(BaseModel):
|
|
94
133
|
"""Request body for creating a component of any kind.
|
|
95
134
|
|
|
@@ -154,37 +193,34 @@ class ComponentResponse(BaseModel):
|
|
|
154
193
|
) -> ComponentResponse:
|
|
155
194
|
"""Convert a component row to its response model.
|
|
156
195
|
|
|
157
|
-
``status`` is the usability state hydration gates
|
|
158
|
-
(
|
|
159
|
-
|
|
160
|
-
when *include_config* is set
|
|
161
|
-
carries just the schema's
|
|
162
|
-
as a connection's
|
|
163
|
-
``
|
|
164
|
-
|
|
165
|
-
request failing over one
|
|
196
|
+
The row is read once: ``status`` is the usability state hydration gates
|
|
197
|
+
on (catalog resolution, then whether the payload decodes) and every
|
|
198
|
+
view of the payload comes off that same reading. Secret kinds expose
|
|
199
|
+
their decoded payload as ``config`` only when *include_config* is set
|
|
200
|
+
(detail responses); otherwise ``config`` carries just the schema's
|
|
201
|
+
``x-public`` subset (operational fields such as a connection's
|
|
202
|
+
``auto_renew``). An ``unreadable`` row carries no ``config`` either
|
|
203
|
+
way: the reason rides its ``status``, so the collection still lists and
|
|
204
|
+
the UI can say what is wrong instead of the request failing over one
|
|
205
|
+
row.
|
|
166
206
|
|
|
167
207
|
Args:
|
|
168
208
|
row: The component row to convert.
|
|
169
209
|
store: The Store instance.
|
|
170
210
|
include_config: Whether a secret kind's decoded config is exposed.
|
|
171
|
-
parent_key: The
|
|
172
|
-
sparing a
|
|
173
|
-
with_children: Whether the row
|
|
211
|
+
parent_key: The owner's key when the caller already knows it,
|
|
212
|
+
sparing a lookup for an owned row.
|
|
213
|
+
with_children: Whether the components the row owns are nested in
|
|
214
|
+
the response.
|
|
174
215
|
|
|
175
216
|
Returns:
|
|
176
217
|
The response model.
|
|
177
218
|
"""
|
|
178
|
-
|
|
219
|
+
reading = store.components.read(row, parent_key=parent_key)
|
|
179
220
|
|
|
180
|
-
config
|
|
181
|
-
if KINDS[row.kind].sensitive:
|
|
182
|
-
if status is ComponentStatus.UNREADABLE
|
|
183
|
-
config = None
|
|
184
|
-
elif include_config:
|
|
185
|
-
config = store.components.decode_config(row)
|
|
186
|
-
else:
|
|
187
|
-
config = store.components.public_config(row)
|
|
221
|
+
config = reading.config
|
|
222
|
+
if KINDS[row.kind].sensitive and not include_config:
|
|
223
|
+
config = None if reading.status is ComponentStatus.UNREADABLE else reading.public_config
|
|
188
224
|
|
|
189
225
|
return cls(
|
|
190
226
|
id=row.id,
|
|
@@ -192,8 +228,8 @@ class ComponentResponse(BaseModel):
|
|
|
192
228
|
kind=row.kind,
|
|
193
229
|
key=row.key,
|
|
194
230
|
name=row.name,
|
|
195
|
-
discriminator=
|
|
196
|
-
status=status,
|
|
231
|
+
discriminator=reading.discriminator,
|
|
232
|
+
status=reading.status,
|
|
197
233
|
config=config,
|
|
198
234
|
state=row.state,
|
|
199
235
|
encrypted=row.encrypted,
|
|
@@ -242,7 +278,12 @@ def _relations_of(row: Component) -> dict[str, list[RelationRef]]:
|
|
|
242
278
|
grouped: dict[str, list[RelationRef]] = {}
|
|
243
279
|
for relation in row.out_relations:
|
|
244
280
|
grouped.setdefault(relation.name, []).append(
|
|
245
|
-
RelationRef(
|
|
281
|
+
RelationRef(
|
|
282
|
+
dst_id=relation.dst_id,
|
|
283
|
+
dst_kind=relation.dst_kind,
|
|
284
|
+
dst_key=relation.dst.key if relation.dst else "",
|
|
285
|
+
dst_name=relation.dst.name if relation.dst else None,
|
|
286
|
+
)
|
|
246
287
|
)
|
|
247
288
|
return grouped
|
|
248
289
|
|
|
@@ -272,18 +313,22 @@ def list_components(
|
|
|
272
313
|
store: StoreDep,
|
|
273
314
|
kind: Annotated[list[str] | None, Query()] = None,
|
|
274
315
|
) -> list[ComponentResponse]:
|
|
275
|
-
"""List the organisation's components, optionally filtered by kind(s).
|
|
316
|
+
"""List the organisation's root components, optionally filtered by kind(s).
|
|
317
|
+
|
|
318
|
+
An owned component (a source's asset) rides under its owner's
|
|
319
|
+
``children`` rather than listing on its own, so ``kind=asset`` yields the
|
|
320
|
+
standalone assets alone.
|
|
276
321
|
|
|
277
322
|
Args:
|
|
278
|
-
kind: The
|
|
323
|
+
kind: The root kinds to keep; None lists every kind.
|
|
279
324
|
user: The authenticated user.
|
|
280
325
|
org_id: The active organisation UUID.
|
|
281
326
|
store: The Store instance.
|
|
282
327
|
|
|
283
328
|
Returns:
|
|
284
|
-
The organisation's components, secret configs withheld.
|
|
329
|
+
The organisation's root components, secret configs withheld.
|
|
285
330
|
"""
|
|
286
|
-
rows = store.components.
|
|
331
|
+
rows = store.components.list_roots(org_id, kinds=kind)
|
|
287
332
|
return [ComponentResponse.from_row(row, store, include_config=False) for row in rows]
|
|
288
333
|
|
|
289
334
|
|
|
@@ -321,6 +366,38 @@ def list_relations(
|
|
|
321
366
|
]
|
|
322
367
|
|
|
323
368
|
|
|
369
|
+
@router.get("/delete-impact")
|
|
370
|
+
def get_delete_impact(
|
|
371
|
+
user: CurrentUserDep,
|
|
372
|
+
store: StoreDep,
|
|
373
|
+
component_id: Annotated[list[UUID], Query(alias="id")],
|
|
374
|
+
) -> DeleteImpactResponse:
|
|
375
|
+
"""Preview what deleting the given components does to the components bound to them.
|
|
376
|
+
|
|
377
|
+
The rule the delete guard enforces, evaluated without deleting, so a
|
|
378
|
+
confirmation can say up front what refuses the deletion and what merely
|
|
379
|
+
loses a binding.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
user: The authenticated user.
|
|
383
|
+
store: The Store instance.
|
|
384
|
+
component_id: The components about to be deleted, repeated per id.
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
The blocking and detaching referrers.
|
|
388
|
+
|
|
389
|
+
Raises:
|
|
390
|
+
HTTPException: 404 when any of the ids is unknown.
|
|
391
|
+
"""
|
|
392
|
+
for one in component_id:
|
|
393
|
+
load_authorized(store.components.get, one, user, store, label="Component")
|
|
394
|
+
try:
|
|
395
|
+
impact = store.components.delete_impact(component_id)
|
|
396
|
+
except NotFoundError as e:
|
|
397
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
398
|
+
return DeleteImpactResponse.from_impact(impact)
|
|
399
|
+
|
|
400
|
+
|
|
324
401
|
@router.post("/", status_code=201)
|
|
325
402
|
def create_component(
|
|
326
403
|
body: ComponentCreateRequest,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interloper_api-0.82.0 → interloper_api-0.83.0}/src/interloper_api/notifications/__init__.py
RENAMED
|
File without changes
|
{interloper_api-0.82.0 → interloper_api-0.83.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
|