Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/python/dirsql/_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def _build_db(self):
if config_extensions is not None:
extensions = [*(extensions or []), *config_extensions]
suppress = True
return _RustDirSQL(
return self._new_core(
self._root,
tables=self._tables,
ignore=self._ignore,
Expand All @@ -149,6 +149,9 @@ def _build_db(self):
suppress_config_extensions=suppress,
)

def _new_core(self, root, **kwargs):
return _RustDirSQL(root, **kwargs)

def _resolved_extensions(self):
"""Resolve each programmatic extension's ``path`` to a loadable file.

Expand Down
8 changes: 6 additions & 2 deletions packages/python/dirsql/_async_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,18 @@ def describe_ready_and_query():
@pytest.mark.asyncio
async def it_uses_the_background_db():
with (
patch.object(async_mod, "_RustDirSQL", _FakeRustDirSQL),
patch.object(
async_mod,
"resolve_extension_path",
side_effect=lambda path, base, resolve_relative: f"R:{path}",
) as resolver,
):
db = async_mod.DirSQL(

class InjectedDirSQL(async_mod.DirSQL):
def _new_core(self, root, **kwargs):
return _FakeRustDirSQL(root, **kwargs)

db = InjectedDirSQL(
"/tmp/root",
tables=["table-a"],
ignore=["**/*.tmp"],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"command": "uv run python -m pytest tests/e2e/capture_collision_test.py tests/e2e/fanout_test.py tests/e2e/cli_version_test.py tests/e2e/extension_package_test.py tests/e2e/on_file_query_test.py tests/e2e/sigint_interrupt_test.py tests/e2e/plugin_extension_discovery_test.py tests/e2e/plugin_discovery_test.py tests/e2e/query_contract_test.py -x -q",
"ran_at": 1791302200,
"exit_code": 0,
"commit": "1553e866cb8da7050a1c444833e269c831f6e0a1",
"branch": "claude/1330-python-dirsql-patch"
}
11 changes: 0 additions & 11 deletions packages/python/testing-conventions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,6 @@ extra_scope = ["packages/rust/src", "packages/python/src", "packages/python/test
fail_under = 100
branch = true

# The hermetic integration tier is defined by patching the first-party core
# boundary: `dirsql._async._RustDirSQL` is the PyO3 class, and replacing it is
# what lets these tests run SQLite-free and native-build-free. There is no
# third-party target to re-root onto, so `no-first-party-patch` has nothing to
# say about this seam. Every other patch in both files targets stdlib and stays
# gated.
[[python.exempt]]
path = "tests/integration/hermetic/dirsql_test.py"
rules = ["no-first-party-patch"]
reason = "the hermetic tier IS the _RustDirSQL core-boundary patch; no third-party target exists to move it to"

[[python.exempt]]
path = "tests/integration/hermetic/extensions_test.py"
rules = ["no-first-party-patch"]
Expand Down
56 changes: 31 additions & 25 deletions packages/python/tests/integration/hermetic/dirsql_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,8 @@ def _reset_instances():

@pytest.fixture
def mock_core():
"""Replace the Rust-backed ``_RustDirSQL`` alias in ``dirsql._async``."""
with patch.object(async_mod, "_RustDirSQL", _FakeRustDirSQL):
yield _FakeRustDirSQL
"""Build the wrapper with a fake core through its factory seam."""
return _wrapper_with_core(_FakeRustDirSQL)


@pytest.fixture
Expand All @@ -110,7 +109,7 @@ async def spy(func, *args, **kwargs):

@pytest.fixture
def core_init_raises(request):
"""Patch the Rust core with a constructor that raises ``request.param``.
"""Build the wrapper with a core constructor that raises ``request.param``.

Parametrize indirectly with the exception instance the core should
raise on construction (init / config-load failure paths).
Expand All @@ -121,8 +120,15 @@ class Boom(_FakeRustDirSQL):
def __init__(self, *a, **kw):
raise exc

with patch.object(async_mod, "_RustDirSQL", Boom):
yield exc
return _wrapper_with_core(Boom)


def _wrapper_with_core(core_factory):
class WrapperWithCore(async_mod.DirSQL):
def _new_core(self, root, **kwargs):
return core_factory(root, **kwargs)

return WrapperWithCore


def describe_binding_layer():
Expand All @@ -131,14 +137,14 @@ def describe_async_offloading():
# packages/python/README.md ("DirSQL is async by default").
@pytest.mark.asyncio
async def it_offloads_init_via_to_thread(mock_core, to_thread_spy):
db = async_mod.DirSQL("/root", tables=["t"])
db = mock_core("/root", tables=["t"])
await db.ready()

assert "_build_db" in to_thread_spy, to_thread_spy

@pytest.mark.asyncio
async def it_offloads_query_via_to_thread(mock_core, to_thread_spy):
db = async_mod.DirSQL("/root", tables=["t"])
db = mock_core("/root", tables=["t"])
await db.ready()

await db.query("SELECT 1")
Expand All @@ -152,13 +158,13 @@ def describe_ready():
"core_init_raises", [RuntimeError("init failed")], indirect=True
)
async def it_surfaces_init_exceptions(core_init_raises):
db = async_mod.DirSQL("/root", tables=["t"])
db = core_init_raises("/root", tables=["t"])
with pytest.raises(RuntimeError, match="init failed"):
await db.ready()

@pytest.mark.asyncio
async def it_is_safe_to_call_repeatedly(mock_core):
db = async_mod.DirSQL("/root", tables=["t"])
db = mock_core("/root", tables=["t"])
await db.ready()
await db.ready()
await db.ready()
Expand All @@ -169,7 +175,7 @@ async def it_is_safe_to_call_repeatedly(mock_core):
"core_init_raises", [ValueError("bad config")], indirect=True
)
async def it_re_raises_init_error_on_every_ready_call(core_init_raises):
db = async_mod.DirSQL("/root", tables=["t"])
db = core_init_raises("/root", tables=["t"])
with pytest.raises(ValueError):
await db.ready()
with pytest.raises(ValueError):
Expand All @@ -180,7 +186,7 @@ def describe_query():
# docs/reference/sdk.md and packages/python/README.md.
@pytest.mark.asyncio
async def it_passes_sql_through_untouched(mock_core):
db = async_mod.DirSQL("/root", tables=["t"])
db = mock_core("/root", tables=["t"])
await db.ready()

sql = "SELECT name, age FROM users WHERE age > 30 -- comment"
Expand All @@ -193,7 +199,7 @@ async def it_passes_sql_through_untouched(mock_core):
async def it_passes_a_path_table_name_through_untouched(mock_core):
# Path-table resolution lives in the core; the SDK must not
# rewrite, quote, or normalize the name on the way down.
db = async_mod.DirSQL("/root", tables=["t"])
db = mock_core("/root", tables=["t"])
await db.ready()

sql = "SELECT basename FROM './docs/*.md'"
Expand All @@ -206,7 +212,7 @@ def describe_watch():
# docs/reference/sdk.md and packages/python/README.md.
@pytest.mark.asyncio
async def it_lazily_starts_watcher_on_first_iteration(mock_core):
db = async_mod.DirSQL("/root", tables=["t"])
db = mock_core("/root", tables=["t"])
await db.ready()

stream = db.watch()
Expand All @@ -220,7 +226,7 @@ async def it_lazily_starts_watcher_on_first_iteration(mock_core):

@pytest.mark.asyncio
async def it_drains_buffered_events_before_polling_again(mock_core):
db = async_mod.DirSQL("/root", tables=["t"])
db = mock_core("/root", tables=["t"])
await db.ready()

fake = _FakeRustDirSQL.instances[0]
Expand All @@ -235,7 +241,7 @@ async def it_drains_buffered_events_before_polling_again(mock_core):

@pytest.mark.asyncio
async def it_polls_until_events_arrive(mock_core):
db = async_mod.DirSQL("/root", tables=["t"])
db = mock_core("/root", tables=["t"])
await db.ready()

fake = _FakeRustDirSQL.instances[0]
Expand All @@ -251,7 +257,7 @@ def describe_config_kwarg():
# docs/reference/config.md and packages/python/README.md.
@pytest.mark.asyncio
async def it_forwards_config_path_to_core(mock_core):
db = async_mod.DirSQL(config="/some/.dirsql.toml")
db = mock_core(config="/some/.dirsql.toml")
await db.ready()

inst = _FakeRustDirSQL.instances[-1]
Expand All @@ -266,7 +272,7 @@ async def it_forwards_config_path_to_core(mock_core):
"core_init_raises", [FileNotFoundError("/missing.toml")], indirect=True
)
async def it_surfaces_config_load_errors(core_init_raises):
db = async_mod.DirSQL(config="/missing.toml")
db = core_init_raises(config="/missing.toml")
with pytest.raises(FileNotFoundError):
await db.ready()

Expand All @@ -276,7 +282,7 @@ async def it_forwards_construction_without_root_or_config_to_the_core(
):
# With neither root nor config the core roots at the cwd; the
# wrapper forwards both as None.
db = async_mod.DirSQL()
db = mock_core()
await db.ready()

inst = _FakeRustDirSQL.instances[-1]
Expand All @@ -289,7 +295,7 @@ def describe_ignore_kwarg():
@pytest.mark.asyncio
async def it_forwards_ignore_to_core(mock_core):
ignore = ["**/node_modules/**", ".git"]
db = async_mod.DirSQL("/root", tables=["t"], ignore=ignore)
db = mock_core("/root", tables=["t"], ignore=ignore)
await db.ready()

inst = _FakeRustDirSQL.instances[0]
Expand All @@ -299,7 +305,7 @@ async def it_forwards_ignore_to_core(mock_core):

@pytest.mark.asyncio
async def it_defaults_ignore_to_none(mock_core):
db = async_mod.DirSQL("/root", tables=["t"])
db = mock_core("/root", tables=["t"])
await db.ready()
assert _FakeRustDirSQL.instances[0].ignore is None

Expand All @@ -308,14 +314,14 @@ def describe_no_ignore_kwarg():
# docs/reference/path-tables.md.
@pytest.mark.asyncio
async def it_forwards_no_ignore_to_core(mock_core):
db = async_mod.DirSQL("/root", no_ignore=True)
db = mock_core("/root", no_ignore=True)
await db.ready()

assert _FakeRustDirSQL.instances[0].no_ignore is True

@pytest.mark.asyncio
async def it_defaults_no_ignore_to_false(mock_core):
db = async_mod.DirSQL("/root")
db = mock_core("/root")
await db.ready()

assert _FakeRustDirSQL.instances[0].no_ignore is False
Expand All @@ -324,7 +330,7 @@ def describe_persist_kwargs():
# Feature: persist / persist_path. See docs/howto/persist.md.
@pytest.mark.asyncio
async def it_forwards_persist_kwargs_to_core(mock_core):
db = async_mod.DirSQL(
db = mock_core(
"/root",
tables=["t"],
persist=True,
Expand All @@ -337,7 +343,7 @@ async def it_forwards_persist_kwargs_to_core(mock_core):

@pytest.mark.asyncio
async def it_defaults_persist_to_false(mock_core):
db = async_mod.DirSQL("/root", tables=["t"])
db = mock_core("/root", tables=["t"])
await db.ready()
inst = _FakeRustDirSQL.instances[0]
assert inst.persist is False
Expand Down
Loading