interloper-toolkit 0.76.0__tar.gz → 0.78.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_toolkit-0.76.0 → interloper_toolkit-0.78.0}/PKG-INFO +1 -1
- {interloper_toolkit-0.76.0 → interloper_toolkit-0.78.0}/pyproject.toml +4 -2
- {interloper_toolkit-0.76.0 → interloper_toolkit-0.78.0}/pyproject.toml.orig +3 -3
- interloper_toolkit-0.78.0/src/interloper_toolkit/__init__.py +23 -0
- {interloper_toolkit-0.76.0 → interloper_toolkit-0.78.0}/src/interloper_toolkit/catalog.py +1 -1
- interloper_toolkit-0.78.0/src/interloper_toolkit/collection.py +120 -0
- {interloper_toolkit-0.76.0 → interloper_toolkit-0.78.0}/src/interloper_toolkit/lineage.py +12 -12
- {interloper_toolkit-0.76.0 → interloper_toolkit-0.78.0}/src/interloper_toolkit/models.py +23 -4
- interloper_toolkit-0.76.0/src/interloper_toolkit/__init__.py +0 -14
- interloper_toolkit-0.76.0/src/interloper_toolkit/collection.py +0 -60
- {interloper_toolkit-0.76.0 → interloper_toolkit-0.78.0}/README.md +0 -0
- {interloper_toolkit-0.76.0 → interloper_toolkit-0.78.0}/src/interloper_toolkit/analytics.py +0 -0
- {interloper_toolkit-0.76.0 → interloper_toolkit-0.78.0}/src/interloper_toolkit/context.py +0 -0
- {interloper_toolkit-0.76.0 → interloper_toolkit-0.78.0}/src/interloper_toolkit/scheduling.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "interloper-toolkit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.78.0"
|
|
4
4
|
description = "Interloper shared read-only tool functions for AI surfaces (agent, MCP)"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -14,7 +14,7 @@ name = "Guillaume Onfroy"
|
|
|
14
14
|
email = "guillaume@digitlcloud.com"
|
|
15
15
|
|
|
16
16
|
[build-system]
|
|
17
|
-
requires = ["uv_build>=0.
|
|
17
|
+
requires = ["uv_build>=0.12.9,<0.13"]
|
|
18
18
|
build-backend = "uv_build"
|
|
19
19
|
|
|
20
20
|
[tool.uv.sources.interloper-core]
|
|
@@ -54,6 +54,8 @@ convention = "google"
|
|
|
54
54
|
"D102",
|
|
55
55
|
"D103",
|
|
56
56
|
"D104",
|
|
57
|
+
"RUF069",
|
|
58
|
+
"PLW0108",
|
|
57
59
|
]
|
|
58
60
|
"src/interloper_toolkit/{analytics,catalog,collection,lineage,scheduling}.py" = [
|
|
59
61
|
"D417",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# ###############
|
|
4
4
|
[project]
|
|
5
5
|
name = "interloper-toolkit"
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.78.0"
|
|
7
7
|
description = "Interloper shared read-only tool functions for AI surfaces (agent, MCP)"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
authors = [{ name = "Guillaume Onfroy", email = "guillaume@digitlcloud.com" }]
|
|
@@ -14,7 +14,7 @@ dependencies = [
|
|
|
14
14
|
]
|
|
15
15
|
|
|
16
16
|
[build-system]
|
|
17
|
-
requires = ["uv_build>=0.
|
|
17
|
+
requires = ["uv_build>=0.12.9,<0.13"]
|
|
18
18
|
build-backend = "uv_build"
|
|
19
19
|
|
|
20
20
|
[tool.uv.sources]
|
|
@@ -36,5 +36,5 @@ convention = "google"
|
|
|
36
36
|
|
|
37
37
|
[tool.ruff.lint.per-file-ignores]
|
|
38
38
|
"__init__.py" = ["F401", "F403"]
|
|
39
|
-
"tests/**" = ["ANN", "F811", "D101", "D102", "D103", "D104"]
|
|
39
|
+
"tests/**" = ["ANN", "F811", "D101", "D102", "D103", "D104", "RUF069", "PLW0108"]
|
|
40
40
|
"src/interloper_toolkit/{analytics,catalog,collection,lineage,scheduling}.py" = ["D417", "DOC201", "BLE001"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Tool functions shared by AI surfaces (agent, MCP server).
|
|
2
|
+
|
|
3
|
+
Every function takes a :class:`~interloper_toolkit.context.ToolkitContext`
|
|
4
|
+
as its first argument and returns ``<SuccessModel> | ToolError`` — typed
|
|
5
|
+
pydantic results (see :mod:`interloper_toolkit.models`) discriminated by
|
|
6
|
+
the literal ``status`` field, never raising. The docstrings are LLM-facing:
|
|
7
|
+
both the ADK agent and the MCP server surface them verbatim as tool
|
|
8
|
+
descriptions.
|
|
9
|
+
|
|
10
|
+
Almost every function here is read-only; the sole exceptions are
|
|
11
|
+
:func:`interloper_toolkit.collection.bind_relation` and
|
|
12
|
+
:func:`interloper_toolkit.collection.unbind_relation`, which write and are
|
|
13
|
+
re-exported here as this package's whole write surface. A surface that must
|
|
14
|
+
stay read-only (the MCP server's own registration is one) never registers
|
|
15
|
+
those two; the ADK agent, whose own write tools already live beside them,
|
|
16
|
+
does.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from interloper_toolkit.collection import bind_relation, unbind_relation
|
|
20
|
+
from interloper_toolkit.context import ToolkitContext, serialize
|
|
21
|
+
from interloper_toolkit.models import ToolError
|
|
22
|
+
|
|
23
|
+
__all__ = ["ToolError", "ToolkitContext", "bind_relation", "serialize", "unbind_relation"]
|
|
@@ -122,7 +122,7 @@ def list_definitions(
|
|
|
122
122
|
def get_definition(ctx: ToolkitContext, key: str) -> DefinitionDetail | ToolError:
|
|
123
123
|
"""Get a component definition's full catalog detail.
|
|
124
124
|
|
|
125
|
-
For a source this includes the config schema,
|
|
125
|
+
For a source this includes the config schema, declared relations,
|
|
126
126
|
destination types, and all its assets with their schemas. This is the
|
|
127
127
|
catalog definition (the component *type*), not an instance from the
|
|
128
128
|
org's collection.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Collection tools: the org's component instances.
|
|
2
|
+
|
|
3
|
+
``list_components`` is read-only, shared with surfaces that must stay
|
|
4
|
+
read-only (kind-specific creation and connection operations stay with the
|
|
5
|
+
agent). ``bind_relation`` and ``unbind_relation`` are the exception: they
|
|
6
|
+
write, generically over every kind's declared relations, so a caller must
|
|
7
|
+
never register them alongside a read-only tool set (see
|
|
8
|
+
``interloper_mcp.tools``, deliberately read-only), only wherever that
|
|
9
|
+
surface's own write tools already live.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from uuid import UUID
|
|
15
|
+
|
|
16
|
+
from interloper.component import KINDS
|
|
17
|
+
from interloper.errors import ConfigError, NotFoundError
|
|
18
|
+
|
|
19
|
+
from interloper_toolkit.context import ToolkitContext
|
|
20
|
+
from interloper_toolkit.models import (
|
|
21
|
+
BindResult,
|
|
22
|
+
ComponentCounts,
|
|
23
|
+
ComponentList,
|
|
24
|
+
ComponentSummary,
|
|
25
|
+
ToolError,
|
|
26
|
+
UnbindResult,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def list_components(
|
|
31
|
+
ctx: ToolkitContext, kind: str | None = None
|
|
32
|
+
) -> ComponentCounts | ComponentList | ToolError:
|
|
33
|
+
"""List the components in the organisation's collection.
|
|
34
|
+
|
|
35
|
+
This answers "what do we have?" — what *could* be added (the catalog of
|
|
36
|
+
definitions) is the Catalog specialist's domain. Sensitive kinds
|
|
37
|
+
(connections, configs, resources) always return identity and metadata
|
|
38
|
+
only — never credential or config values.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
kind: Component kind to list — e.g. 'source', 'connection',
|
|
42
|
+
'destination'. Omit for per-kind counts only; call again with a
|
|
43
|
+
kind for the entries.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
if kind is None:
|
|
47
|
+
counts: dict[str, int] = {}
|
|
48
|
+
for c in ctx.store.components.list_all(ctx.org_id):
|
|
49
|
+
counts[c.kind] = counts.get(c.kind, 0) + 1
|
|
50
|
+
return ComponentCounts(
|
|
51
|
+
component_counts=counts,
|
|
52
|
+
message="Call again with a kind for the entries.",
|
|
53
|
+
)
|
|
54
|
+
if kind not in KINDS:
|
|
55
|
+
return ToolError(error=f"Unknown kind '{kind}'", valid_kinds=sorted(KINDS.keys()))
|
|
56
|
+
|
|
57
|
+
results = []
|
|
58
|
+
for c in ctx.store.components.list_all(ctx.org_id, kinds=[kind]):
|
|
59
|
+
entry = ComponentSummary(
|
|
60
|
+
id=str(c.id),
|
|
61
|
+
key=c.key,
|
|
62
|
+
name=c.name,
|
|
63
|
+
type_name=(ctx.catalog.get(c.key) or {}).get("name", c.key),
|
|
64
|
+
created_at=c.created_at,
|
|
65
|
+
)
|
|
66
|
+
# Fail closed: a sensitive kind's config is (or wraps) credentials.
|
|
67
|
+
if not KINDS[kind].sensitive:
|
|
68
|
+
entry.config = c.config
|
|
69
|
+
if kind == "source":
|
|
70
|
+
entry.asset_count = len(c.children)
|
|
71
|
+
results.append(entry)
|
|
72
|
+
|
|
73
|
+
return ComponentList(kind=kind, count=len(results), components=results)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
return ToolError(error=str(e))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# -- Relations (write) ---------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def bind_relation(ctx: ToolkitContext, component_id: str, name: str, dst_id: str) -> BindResult | ToolError:
|
|
82
|
+
"""Bind one component to another under a declared relation name.
|
|
83
|
+
|
|
84
|
+
Works on any kind: a source's connection, a job's watched assets, a
|
|
85
|
+
destination target, whatever the component's own class declares under
|
|
86
|
+
that name. A ``many`` name accumulates; a single-valued one repoints, so
|
|
87
|
+
rebinding it needs no prior unbind_relation call. Recap what the binding
|
|
88
|
+
changes and get the user's explicit confirmation BEFORE calling this.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
component_id: UUID of the component the relation originates from.
|
|
92
|
+
name: Relation name, which the component's class must declare.
|
|
93
|
+
dst_id: UUID of the destination component the relation points at.
|
|
94
|
+
Must belong to the same organisation as the source.
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
row = ctx.store.relations.add(UUID(component_id), name=name, dst_id=UUID(dst_id))
|
|
98
|
+
except (ConfigError, NotFoundError, ValueError) as e:
|
|
99
|
+
return ToolError(error=str(e))
|
|
100
|
+
return BindResult(src_id=str(row.src_id), name=row.name, dst_id=str(row.dst_id), dst_kind=row.dst_kind)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def unbind_relation(ctx: ToolkitContext, component_id: str, name: str, dst_id: str) -> UnbindResult | ToolError:
|
|
104
|
+
"""Detach one component from another under a declared relation name.
|
|
105
|
+
|
|
106
|
+
A non-optional relation cannot be emptied, only repointed with
|
|
107
|
+
bind_relation. Recap what the component loses and get the user's
|
|
108
|
+
explicit confirmation BEFORE calling this.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
component_id: UUID of the component the relation originates from.
|
|
112
|
+
name: Relation name the edge is filed under.
|
|
113
|
+
dst_id: UUID of the destination the removed edge points at. Removing
|
|
114
|
+
an edge that isn't there is a no-op.
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
ctx.store.relations.remove(UUID(component_id), name=name, dst_id=UUID(dst_id))
|
|
118
|
+
except (ConfigError, NotFoundError, ValueError) as e:
|
|
119
|
+
return ToolError(error=str(e))
|
|
120
|
+
return UnbindResult(src_id=component_id, name=name, dst_id=dst_id)
|
|
@@ -10,11 +10,11 @@ from interloper_toolkit.models import (
|
|
|
10
10
|
AssetRef,
|
|
11
11
|
CrossSourceDependencies,
|
|
12
12
|
CrossSourceEdge,
|
|
13
|
-
DependencyEdge,
|
|
14
13
|
DownstreamResult,
|
|
15
14
|
ImpactAnalysis,
|
|
16
15
|
LineageItem,
|
|
17
16
|
LineageResult,
|
|
17
|
+
RelationEdge,
|
|
18
18
|
ToolError,
|
|
19
19
|
UpstreamResult,
|
|
20
20
|
)
|
|
@@ -27,19 +27,19 @@ def get_upstream(ctx: ToolkitContext, asset_id: str) -> UpstreamResult | ToolErr
|
|
|
27
27
|
asset_id: UUID of the asset to inspect.
|
|
28
28
|
|
|
29
29
|
Returns the list of upstream assets that this asset depends on,
|
|
30
|
-
including the
|
|
30
|
+
including the relation name used for each dependency.
|
|
31
31
|
"""
|
|
32
32
|
try:
|
|
33
|
-
deps = ctx.store.relations.list_all(ctx.org_id,
|
|
33
|
+
deps = ctx.store.relations.list_all(ctx.org_id, src_kind="asset", dst_kind="asset")
|
|
34
34
|
target = UUID(asset_id)
|
|
35
35
|
|
|
36
36
|
upstream = []
|
|
37
37
|
for dep in deps:
|
|
38
38
|
if dep.src_id == target:
|
|
39
39
|
asset = ctx.store.components.get(dep.dst_id, kind="asset")
|
|
40
|
-
upstream.append(
|
|
40
|
+
upstream.append(RelationEdge(
|
|
41
41
|
asset_id=str(dep.dst_id),
|
|
42
|
-
param_name=dep.
|
|
42
|
+
param_name=dep.name,
|
|
43
43
|
asset_key=asset.key,
|
|
44
44
|
source_id=str(asset.parent_id),
|
|
45
45
|
))
|
|
@@ -58,16 +58,16 @@ def get_downstream(ctx: ToolkitContext, asset_id: str) -> DownstreamResult | Too
|
|
|
58
58
|
Returns the list of assets that directly depend on this asset.
|
|
59
59
|
"""
|
|
60
60
|
try:
|
|
61
|
-
deps = ctx.store.relations.list_all(ctx.org_id,
|
|
61
|
+
deps = ctx.store.relations.list_all(ctx.org_id, src_kind="asset", dst_kind="asset")
|
|
62
62
|
target = UUID(asset_id)
|
|
63
63
|
|
|
64
64
|
downstream = []
|
|
65
65
|
for dep in deps:
|
|
66
66
|
if dep.dst_id == target:
|
|
67
67
|
asset = ctx.store.components.get(dep.src_id, kind="asset")
|
|
68
|
-
downstream.append(
|
|
68
|
+
downstream.append(RelationEdge(
|
|
69
69
|
asset_id=str(dep.src_id),
|
|
70
|
-
param_name=dep.
|
|
70
|
+
param_name=dep.name,
|
|
71
71
|
asset_key=asset.key,
|
|
72
72
|
source_id=str(asset.parent_id),
|
|
73
73
|
))
|
|
@@ -165,7 +165,7 @@ def cross_source_dependencies(ctx: ToolkitContext) -> CrossSourceDependencies |
|
|
|
165
165
|
different sources.
|
|
166
166
|
"""
|
|
167
167
|
try:
|
|
168
|
-
deps = ctx.store.relations.list_all(ctx.org_id,
|
|
168
|
+
deps = ctx.store.relations.list_all(ctx.org_id, src_kind="asset", dst_kind="asset")
|
|
169
169
|
assets = ctx.store.components.list_all(ctx.org_id, kinds=["asset"])
|
|
170
170
|
|
|
171
171
|
asset_source: dict[UUID, UUID | None] = {}
|
|
@@ -186,7 +186,7 @@ def cross_source_dependencies(ctx: ToolkitContext) -> CrossSourceDependencies |
|
|
|
186
186
|
downstream=asset_info.get(dep.src_id, AssetRef()),
|
|
187
187
|
upstream_asset_id=str(dep.dst_id),
|
|
188
188
|
upstream=asset_info.get(dep.dst_id, AssetRef()),
|
|
189
|
-
param_name=dep.
|
|
189
|
+
param_name=dep.name,
|
|
190
190
|
))
|
|
191
191
|
|
|
192
192
|
return CrossSourceDependencies(cross_source_count=len(cross_deps), dependencies=cross_deps)
|
|
@@ -201,7 +201,7 @@ def _build_adjacency(
|
|
|
201
201
|
ctx: ToolkitContext,
|
|
202
202
|
direction: str,
|
|
203
203
|
) -> tuple[dict[UUID, list[UUID]], dict[UUID, dict[str, str]]]:
|
|
204
|
-
"""Build an adjacency map and asset info lookup from all
|
|
204
|
+
"""Build an adjacency map and asset info lookup from all asset-to-asset relations.
|
|
205
205
|
|
|
206
206
|
Args:
|
|
207
207
|
ctx: The toolkit context.
|
|
@@ -211,7 +211,7 @@ def _build_adjacency(
|
|
|
211
211
|
``(adjacency_map, asset_info_map)`` — info values feed
|
|
212
212
|
:class:`LineageItem` kwargs (asset_key, source_id, source_key).
|
|
213
213
|
"""
|
|
214
|
-
deps = ctx.store.relations.list_all(ctx.org_id,
|
|
214
|
+
deps = ctx.store.relations.list_all(ctx.org_id, src_kind="asset", dst_kind="asset")
|
|
215
215
|
assets = ctx.store.components.list_all(ctx.org_id, kinds=["asset"])
|
|
216
216
|
|
|
217
217
|
asset_info: dict[UUID, dict[str, str]] = {}
|
|
@@ -180,11 +180,30 @@ class ComponentList(BaseModel):
|
|
|
180
180
|
components: list[ComponentSummary]
|
|
181
181
|
|
|
182
182
|
|
|
183
|
+
class BindResult(BaseModel):
|
|
184
|
+
"""One relation edge created or repointed by ``bind_relation``."""
|
|
185
|
+
|
|
186
|
+
status: Literal["success"] = "success"
|
|
187
|
+
src_id: str
|
|
188
|
+
name: str
|
|
189
|
+
dst_id: str
|
|
190
|
+
dst_kind: str
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class UnbindResult(BaseModel):
|
|
194
|
+
"""One relation edge removed by ``unbind_relation``."""
|
|
195
|
+
|
|
196
|
+
status: Literal["success"] = "success"
|
|
197
|
+
src_id: str
|
|
198
|
+
name: str
|
|
199
|
+
dst_id: str
|
|
200
|
+
|
|
201
|
+
|
|
183
202
|
# -- Lineage --------------------------------------------------------------------
|
|
184
203
|
|
|
185
204
|
|
|
186
|
-
class
|
|
187
|
-
"""A direct
|
|
205
|
+
class RelationEdge(BaseModel):
|
|
206
|
+
"""A direct asset-to-asset relation edge from the perspective of one asset."""
|
|
188
207
|
|
|
189
208
|
asset_id: str
|
|
190
209
|
param_name: str
|
|
@@ -197,7 +216,7 @@ class UpstreamResult(BaseModel):
|
|
|
197
216
|
|
|
198
217
|
status: Literal["success"] = "success"
|
|
199
218
|
asset_id: str
|
|
200
|
-
upstream: list[
|
|
219
|
+
upstream: list[RelationEdge]
|
|
201
220
|
|
|
202
221
|
|
|
203
222
|
class DownstreamResult(BaseModel):
|
|
@@ -205,7 +224,7 @@ class DownstreamResult(BaseModel):
|
|
|
205
224
|
|
|
206
225
|
status: Literal["success"] = "success"
|
|
207
226
|
asset_id: str
|
|
208
|
-
downstream: list[
|
|
227
|
+
downstream: list[RelationEdge]
|
|
209
228
|
|
|
210
229
|
|
|
211
230
|
class LineageItem(BaseModel):
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
"""Read-only tool functions shared by AI surfaces (agent, MCP server).
|
|
2
|
-
|
|
3
|
-
Every function takes a :class:`~interloper_toolkit.context.ToolkitContext`
|
|
4
|
-
as its first argument and returns ``<SuccessModel> | ToolError`` — typed
|
|
5
|
-
pydantic results (see :mod:`interloper_toolkit.models`) discriminated by
|
|
6
|
-
the literal ``status`` field, never raising. The docstrings are LLM-facing:
|
|
7
|
-
both the ADK agent and the MCP server surface them verbatim as tool
|
|
8
|
-
descriptions.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from interloper_toolkit.context import ToolkitContext, serialize
|
|
12
|
-
from interloper_toolkit.models import ToolError
|
|
13
|
-
|
|
14
|
-
__all__ = ["ToolError", "ToolkitContext", "serialize"]
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"""Collection tools — the org's component instances, read-only.
|
|
2
|
-
|
|
3
|
-
Creation and connection operations stay with the agent; this module is
|
|
4
|
-
shared with surfaces that must stay read-only.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from interloper.component import KINDS
|
|
10
|
-
|
|
11
|
-
from interloper_toolkit.context import ToolkitContext
|
|
12
|
-
from interloper_toolkit.models import ComponentCounts, ComponentList, ComponentSummary, ToolError
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def list_components(
|
|
16
|
-
ctx: ToolkitContext, kind: str | None = None
|
|
17
|
-
) -> ComponentCounts | ComponentList | ToolError:
|
|
18
|
-
"""List the components in the organisation's collection.
|
|
19
|
-
|
|
20
|
-
This answers "what do we have?" — what *could* be added (the catalog of
|
|
21
|
-
definitions) is the Catalog specialist's domain. Sensitive kinds
|
|
22
|
-
(connections, configs, resources) always return identity and metadata
|
|
23
|
-
only — never credential or config values.
|
|
24
|
-
|
|
25
|
-
Args:
|
|
26
|
-
kind: Component kind to list — e.g. 'source', 'connection',
|
|
27
|
-
'destination'. Omit for per-kind counts only; call again with a
|
|
28
|
-
kind for the entries.
|
|
29
|
-
"""
|
|
30
|
-
try:
|
|
31
|
-
if kind is None:
|
|
32
|
-
counts: dict[str, int] = {}
|
|
33
|
-
for c in ctx.store.components.list_all(ctx.org_id):
|
|
34
|
-
counts[c.kind] = counts.get(c.kind, 0) + 1
|
|
35
|
-
return ComponentCounts(
|
|
36
|
-
component_counts=counts,
|
|
37
|
-
message="Call again with a kind for the entries.",
|
|
38
|
-
)
|
|
39
|
-
if kind not in KINDS:
|
|
40
|
-
return ToolError(error=f"Unknown kind '{kind}'", valid_kinds=sorted(KINDS.keys()))
|
|
41
|
-
|
|
42
|
-
results = []
|
|
43
|
-
for c in ctx.store.components.list_all(ctx.org_id, kinds=[kind]):
|
|
44
|
-
entry = ComponentSummary(
|
|
45
|
-
id=str(c.id),
|
|
46
|
-
key=c.key,
|
|
47
|
-
name=c.name,
|
|
48
|
-
type_name=(ctx.catalog.get(c.key) or {}).get("name", c.key),
|
|
49
|
-
created_at=c.created_at,
|
|
50
|
-
)
|
|
51
|
-
# Fail closed: a sensitive kind's config is (or wraps) credentials.
|
|
52
|
-
if not KINDS[kind].sensitive:
|
|
53
|
-
entry.config = c.config
|
|
54
|
-
if kind == "source":
|
|
55
|
-
entry.asset_count = len(c.children)
|
|
56
|
-
results.append(entry)
|
|
57
|
-
|
|
58
|
-
return ComponentList(kind=kind, count=len(results), components=results)
|
|
59
|
-
except Exception as e:
|
|
60
|
-
return ToolError(error=str(e))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interloper_toolkit-0.76.0 → interloper_toolkit-0.78.0}/src/interloper_toolkit/scheduling.py
RENAMED
|
File without changes
|