Skip to content

Commit 382ce4f

Browse files
authored
Mount nodes hotfix (#971)
* REF: Reuse existing fixture. * Test mount_node against PG as well. * Fix mount_nodes on PG * Update CHANGELOG * Type hint; move validation; refactor logic for safety.
1 parent 0737029 commit 382ce4f

File tree

4 files changed

+29
-37
lines changed

4 files changed

+29
-37
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ Write the date in place of the "Unreleased" in the case a new version is release
1212
client via a new keyword-only argument `drop_revision` in
1313
`update_metadata`, `patch_metadata`, and `replace_metadata`.
1414

15+
### Fixed
16+
17+
- A critical bug in the `mount_node` feature introduced in the
18+
previous release prohibited the server from starting when
19+
`mount_node` was used with a PostgreSQL database.
20+
1521
## 0.1.0-b25 (2025-05-06)
1622

1723
### Added

tiled/_tests/adapters/test_sql.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
import os
21
from pathlib import Path
3-
from typing import Any, AsyncGenerator, Callable, Generator, Union
2+
from typing import Any, Callable, Generator, Union
43

54
import adbc_driver_duckdb
65
import adbc_driver_sqlite
76
import pyarrow as pa
87
import pytest
9-
import pytest_asyncio
108

119
from tiled.adapters.sql import SQLAdapter, check_table_name
1210
from tiled.storage import parse_storage, register_storage
1311
from tiled.structures.core import StructureFamily
1412
from tiled.structures.data_source import DataSource, Management
1513
from tiled.structures.table import TableStructure
1614

17-
from ..utils import temp_postgres
18-
1915
names = ["f0", "f1", "f2", "f3"]
2016
data0 = [
2117
pa.array([1, 2, 3, 4, 5]),
@@ -157,22 +153,12 @@ def test_attributes_sql_many_part(adapter_sql_many_partition: SQLAdapter) -> Non
157153
)
158154

159155

160-
@pytest_asyncio.fixture
161-
async def postgres_uri() -> AsyncGenerator[str, None]:
162-
uri = os.getenv("TILED_TEST_POSTGRESQL_URI")
163-
if uri is None:
164-
pytest.skip("TILED_TEST_POSTGRESQL_URI is not set")
165-
166-
async with temp_postgres(uri) as uri_with_database_name:
167-
yield uri_with_database_name
168-
169-
170156
@pytest.fixture
171157
def adapter_psql_one_partition(
172158
data_source_from_init_storage: Callable[[str, int], DataSource[TableStructure]],
173-
postgres_uri: str,
159+
postgresql_database_uri: str,
174160
) -> Generator[SQLAdapter, None, None]:
175-
data_source = data_source_from_init_storage(postgres_uri, 1)
161+
data_source = data_source_from_init_storage(postgresql_database_uri, 1)
176162
adapter = SQLAdapter(
177163
data_source.assets[0].data_uri,
178164
data_source.structure,
@@ -186,9 +172,9 @@ def adapter_psql_one_partition(
186172
@pytest.fixture
187173
def adapter_psql_many_partition(
188174
data_source_from_init_storage: Callable[[str, int], DataSource[TableStructure]],
189-
postgres_uri: str,
175+
postgresql_database_uri: str,
190176
) -> SQLAdapter:
191-
data_source = data_source_from_init_storage(postgres_uri, 3)
177+
data_source = data_source_from_init_storage(postgresql_database_uri, 3)
192178
return SQLAdapter(
193179
data_source.assets[0].data_uri,
194180
data_source.structure,

tiled/_tests/test_mount_node.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@
44
from tiled.server.app import build_app_from_config
55

66

7-
def test_mount_node(tmpdir):
7+
def test_mount_node(sqlite_or_postgresql_database_uri, tmpdir):
88
"Test 'mounting' sub-trees of a catalog."
9-
catalog_uri = f"sqlite:///{tmpdir}/catalog.db"
109
one_tree_config = {
1110
"trees": [
1211
{
1312
"path": "/",
1413
"tree": "catalog",
1514
"args": {
16-
"uri": catalog_uri,
15+
"uri": sqlite_or_postgresql_database_uri,
1716
"init_if_not_exists": True,
1817
"writable_storage": [tmpdir / "data"],
1918
},
@@ -35,7 +34,7 @@ def test_mount_node(tmpdir):
3534
"path": "/a",
3635
"tree": "catalog",
3736
"args": {
38-
"uri": catalog_uri,
37+
"uri": sqlite_or_postgresql_database_uri,
3938
"writable_storage": [tmpdir / "data"],
4039
"mount_node": "/A",
4140
},
@@ -44,7 +43,7 @@ def test_mount_node(tmpdir):
4443
"path": "/b",
4544
"tree": "catalog",
4645
"args": {
47-
"uri": catalog_uri,
46+
"uri": sqlite_or_postgresql_database_uri,
4847
"writable_storage": [tmpdir / "data"],
4948
"mount_node": "/B",
5049
},
@@ -80,7 +79,7 @@ def test_mount_node(tmpdir):
8079
"path": "/some/nested/path",
8180
"tree": "catalog",
8281
"args": {
83-
"uri": catalog_uri,
82+
"uri": sqlite_or_postgresql_database_uri,
8483
"writable_storage": [tmpdir / "data"],
8584
"mount_node": "/A/x",
8685
},
@@ -98,7 +97,7 @@ def test_mount_node(tmpdir):
9897
"path": "/some/nested/path",
9998
"tree": "catalog",
10099
"args": {
101-
"uri": catalog_uri,
100+
"uri": sqlite_or_postgresql_database_uri,
102101
"writable_storage": [tmpdir / "data"],
103102
"mount_node": ["A", "x"],
104103
},

tiled/catalog/adapter.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import asyncio
21
import collections
32
import copy
43
import dataclasses
@@ -13,7 +12,7 @@
1312
import uuid
1413
from functools import partial, reduce
1514
from pathlib import Path
16-
from typing import Callable, Dict
15+
from typing import Callable, Dict, List, Optional, Union
1716
from urllib.parse import urlparse
1817

1918
import anyio
@@ -308,9 +307,18 @@ def __init__(
308307
queries=None,
309308
sorting=None,
310309
access_policy=None,
310+
mount_node: Optional[Union[str, List[str]]] = None,
311311
):
312312
self.context = context
313313
self.engine = self.context.engine
314+
if isinstance(mount_node, str):
315+
mount_node = [segment for segment in mount_node.split("/") if segment]
316+
if mount_node:
317+
if not isinstance(node, RootNode):
318+
# sanity-check -- this should not be reachable
319+
raise RuntimeError("mount_node should only be passed with the RootNode")
320+
node.ancestors.extend(mount_node[:-1])
321+
node.key = mount_node[-1]
314322
self.node = node
315323
if node.key is None:
316324
# Special case for RootNode
@@ -1484,7 +1492,7 @@ def from_uri(
14841492
init_if_not_exists=False,
14851493
echo=DEFAULT_ECHO,
14861494
adapters_by_mimetype=None,
1487-
mount_node=None,
1495+
mount_node: Optional[Union[str, List[str]]] = None,
14881496
):
14891497
uri = ensure_specified_sql_driver(uri)
14901498
if init_if_not_exists:
@@ -1528,15 +1536,8 @@ def from_uri(
15281536
Context(engine, writable_storage, readable_storage, adapters_by_mimetype),
15291537
RootNode(metadata, specs, access_policy),
15301538
access_policy=access_policy,
1539+
mount_node=mount_node,
15311540
)
1532-
if isinstance(mount_node, str):
1533-
mount_node = [segment for segment in mount_node.split("/") if segment]
1534-
if mount_node:
1535-
1536-
async def get_nested_node():
1537-
return await adapter.lookup_adapter(mount_node)
1538-
1539-
adapter = asyncio.run(get_nested_node())
15401541
return adapter
15411542

15421543

0 commit comments

Comments
 (0)