Skip to content

Commit f9d97c1

Browse files
committed
feat: list virtual tables
1 parent 979222e commit f9d97c1

File tree

2 files changed

+105
-10
lines changed

2 files changed

+105
-10
lines changed

libs/foundry-dev-tools/src/foundry_dev_tools/clients/tables.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,45 @@ def create_snowflake_table(
101101
return self.api_create_table(
102102
name=name, parent_rid=parent_rid, upstream_config=upstream_config, skip_validation=skip_validation
103103
).json()
104+
105+
def api_list_tables(
106+
self, connection_rid: SourceRid, limit: int | None = 1000, page_token: str | None = None, **kwargs
107+
) -> requests.Response:
108+
"""List Virtual Tables for a source."""
109+
return self.api_request(
110+
"GET",
111+
api_path="tables",
112+
params={
113+
"connectionRid": connection_rid,
114+
**({"limit": limit} if limit else {}),
115+
**({"pageToken": page_token} if page_token else {}),
116+
},
117+
**kwargs,
118+
)
119+
120+
def list_tables(self, connection_rid: SourceRid) -> list[dict]:
121+
"""Returns all tables for a connection/source by handling pagination.
122+
123+
Args:
124+
connection_rid: The rid of the compass Source/Connection.
125+
126+
Returns:
127+
a list of dicts with the following format:
128+
{
129+
'rid': 'ri.tables.main.table.<...>',
130+
'upstream': {
131+
'type': '<type>',
132+
'<type>': {
133+
'connectionRid': 'ri.magritte..source.<...>',
134+
'relation': {'database': 'test', 'schema': 'test', 'table': 'test2'}
135+
}
136+
}
137+
}
138+
"""
139+
tables_response = self.api_list_tables(connection_rid=connection_rid, limit=1000)
140+
response_json = tables_response.json()
141+
tables = response_json["tables"]
142+
while next_page_token := response_json.get("nextPageToken"):
143+
response_json = self.api_list_tables(connection_rid=connection_rid, page_token=next_page_token).json()
144+
tables.extend(response_json["tables"])
145+
return tables

tests/integration/clients/test_magritte_coordinator.py

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from tests.integration.conftest import TEST_SINGLETON
88
from tests.integration.utils import INTEGRATION_TEST_COMPASS_ROOT_RID, INTEGRATION_TEST_EGRESS_POLICY_RID, MARKING_ID
99

10+
SNOWFLAKE_TEST_ACCOUNT_IDENTIFIER = "fdt-integration-test-account1"
11+
1012

1113
@pytest.fixture()
1214
def empty_s3_source() -> api_types.SourceRid:
@@ -20,7 +22,26 @@ def empty_s3_source() -> api_types.SourceRid:
2022

2123
yield source_rid
2224

23-
# Delete test folder
25+
# Delete test source
26+
response = TEST_SINGLETON.ctx.compass.api_delete_permanently(
27+
rids={source_rid}, delete_options={"DO_NOT_REQUIRE_TRASHED"}
28+
)
29+
assert response.status_code == 200
30+
31+
32+
@pytest.fixture()
33+
def empty_snowflake_source() -> api_types.SourceRid:
34+
# Create a new Snowflake
35+
client = TEST_SINGLETON.ctx.magritte_coordinator
36+
rnd = "".join(choice(ascii_uppercase) for _ in range(5))
37+
name = "fdt-test-snowflake_" + rnd
38+
source_rid = client.create_snowflake_source(
39+
name=name, parent_rid=INTEGRATION_TEST_COMPASS_ROOT_RID, account_identifier=SNOWFLAKE_TEST_ACCOUNT_IDENTIFIER
40+
)
41+
42+
yield source_rid
43+
44+
# Delete test source
2445
response = TEST_SINGLETON.ctx.compass.api_delete_permanently(
2546
rids={source_rid}, delete_options={"DO_NOT_REQUIRE_TRASHED"}
2647
)
@@ -208,15 +229,9 @@ def test_export_toggles(empty_s3_source):
208229
}
209230

210231

211-
def test_snowflake():
232+
def test_snowflake(empty_snowflake_source):
212233
client = TEST_SINGLETON.ctx.magritte_coordinator
213-
# Create a new S3 Direct source
214-
rnd = "".join(choice(ascii_uppercase) for _ in range(5))
215-
name = "fdt-test-snowflake_" + rnd
216-
account_identifier = "account1"
217-
source_rid = client.create_snowflake_source(
218-
name=name, parent_rid=INTEGRATION_TEST_COMPASS_ROOT_RID, account_identifier=account_identifier
219-
)
234+
source_rid = empty_snowflake_source
220235

221236
# enable OIDC
222237
client.enable_snowflake_external_oauth(source_rid=source_rid)
@@ -234,7 +249,7 @@ def test_snowflake():
234249
oidc_issuer = client.get_oidc_issuer()
235250
assert as_json["type"] == "cloud"
236251
assert as_json["cloud"]["oidcRuntime"] == {
237-
"audience": f"https://{account_identifier}.snowflakecomputing.com",
252+
"audience": f"https://{SNOWFLAKE_TEST_ACCOUNT_IDENTIFIER}.snowflakecomputing.com",
238253
"issuer": oidc_issuer,
239254
"subject": source_rid,
240255
}
@@ -255,3 +270,41 @@ def test_snowflake():
255270
}
256271

257272
TEST_SINGLETON.ctx.compass.api_delete_permanently(rids={source_rid}, delete_options={"DO_NOT_REQUIRE_TRASHED"})
273+
274+
275+
def test_snowflake_tables(empty_snowflake_source):
276+
client = TEST_SINGLETON.ctx.magritte_coordinator
277+
source_rid = empty_snowflake_source
278+
279+
client.enable_snowflake_external_oauth(source_rid=source_rid)
280+
281+
tables_client = TEST_SINGLETON.ctx.tables
282+
283+
tables = tables_client.list_tables(connection_rid=source_rid)
284+
assert tables == []
285+
286+
table1 = tables_client.create_snowflake_table(
287+
parent_rid=INTEGRATION_TEST_COMPASS_ROOT_RID,
288+
connection_rid=source_rid,
289+
name="fdt-test-snowflake_table" + "".join(choice(ascii_uppercase) for _ in range(5)),
290+
database="test",
291+
schema="test",
292+
table="test1",
293+
skip_validation=True,
294+
)
295+
296+
table2 = tables_client.create_snowflake_table(
297+
parent_rid=INTEGRATION_TEST_COMPASS_ROOT_RID,
298+
connection_rid=source_rid,
299+
name="fdt-test-snowflake_table" + "".join(choice(ascii_uppercase) for _ in range(5)),
300+
database="test",
301+
schema="test",
302+
table="test2",
303+
skip_validation=True,
304+
)
305+
306+
tables = tables_client.list_tables(connection_rid=source_rid)
307+
assert len(tables) == 2
308+
309+
TEST_SINGLETON.ctx.compass.api_delete_permanently(rids={table1, table2}, delete_options={"DO_NOT_REQUIRE_TRASHED"})
310+
TEST_SINGLETON.ctx.compass.api_delete_permanently(rids={source_rid}, delete_options={"DO_NOT_REQUIRE_TRASHED"})

0 commit comments

Comments
 (0)