Skip to content

Commit 9bd52fc

Browse files
lucifer726claude
andauthored
feat(pydantic): forward session properties to the engine (#2684)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent bf2b9ae commit 9bd52fc

2 files changed

Lines changed: 82 additions & 12 deletions

File tree

sdk/wren-pydantic/src/wren_pydantic/_toolkit.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,24 +118,42 @@ def instructions(self, *, toolset: object | None = None) -> str:
118118

119119
# ── Direct Python API (sync only — see module docstring) ──────────────
120120

121-
def query(self, sql: str, limit: int | None = None) -> pa.Table:
122-
"""Execute SQL through the Wren context layer. Returns a pyarrow Table."""
121+
def query(
122+
self,
123+
sql: str,
124+
limit: int | None = None,
125+
properties: dict[str, Any] | None = None,
126+
) -> pa.Table:
127+
"""Execute SQL through the Wren context layer. Returns a pyarrow Table.
128+
129+
``properties`` carries MDL session properties and is forwarded to the
130+
engine's planning path. A model guarded by row-level access control
131+
needs the value its rule declares required — without it planning fails,
132+
so RLAC-protected models cannot be read at all.
133+
"""
123134
engine = self._build_engine()
124135
try:
125-
result = engine.query(sql, limit=limit)
136+
result = engine.query(sql, limit=limit, properties=properties)
126137
finally:
127138
self._connector_cache = engine._connector
128139
return result
129140

130-
def dry_plan(self, sql: str) -> str:
131-
"""Plan SQL through MDL and return the expanded SQL in target dialect."""
132-
return self._build_engine().dry_plan(sql)
141+
def dry_plan(self, sql: str, properties: dict[str, Any] | None = None) -> str:
142+
"""Plan SQL through MDL and return the expanded SQL in target dialect.
143+
144+
``properties`` carries MDL session properties (see :meth:`query`); RLAC
145+
predicates are injected during planning, so they apply here too.
146+
"""
147+
return self._build_engine().dry_plan(sql, properties=properties)
148+
149+
def dry_run(self, sql: str, properties: dict[str, Any] | None = None) -> None:
150+
"""Validate SQL by planning and asking the DB to plan it without executing.
133151
134-
def dry_run(self, sql: str) -> None:
135-
"""Validate SQL by planning and asking the DB to plan it without executing."""
152+
``properties`` carries MDL session properties (see :meth:`query`).
153+
"""
136154
engine = self._build_engine()
137155
try:
138-
engine.dry_run(sql)
156+
engine.dry_run(sql, properties=properties)
139157
finally:
140158
self._connector_cache = engine._connector
141159

sdk/wren-pydantic/tests/unit/test_toolkit_runtime.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def test_query_invokes_wren_engine_with_resolved_manifest(
2626
result = toolkit.query("SELECT 1", limit=10)
2727

2828
assert result is fake_table
29-
fake_engine.query.assert_called_once_with("SELECT 1", limit=10)
29+
fake_engine.query.assert_called_once_with("SELECT 1", limit=10, properties=None)
3030
# Engine constructed with manifest bytes + datasource + connection_info
3131
engine_ctor.assert_called_once()
3232
kwargs = engine_ctor.call_args.kwargs
@@ -106,7 +106,9 @@ def test_dry_plan_delegates_to_engine(tmp_project, fake_active_profile):
106106
result = toolkit.dry_plan("SELECT * FROM orders")
107107

108108
assert result == "SELECT * FROM cte_orders"
109-
fake_engine.dry_plan.assert_called_once_with("SELECT * FROM orders")
109+
fake_engine.dry_plan.assert_called_once_with(
110+
"SELECT * FROM orders", properties=None
111+
)
110112

111113

112114
def test_dry_run_delegates_to_engine(tmp_project, fake_active_profile):
@@ -118,4 +120,54 @@ def test_dry_run_delegates_to_engine(tmp_project, fake_active_profile):
118120
with patch("wren_pydantic._toolkit.WrenEngine", return_value=fake_engine):
119121
toolkit.dry_run("SELECT 1")
120122

121-
fake_engine.dry_run.assert_called_once_with("SELECT 1")
123+
fake_engine.dry_run.assert_called_once_with("SELECT 1", properties=None)
124+
125+
126+
def test_query_forwards_session_properties(tmp_project, fake_active_profile):
127+
"""Session properties reach the engine, so RLAC-protected models are readable."""
128+
fake_engine = MagicMock(name="engine")
129+
fake_engine.query.return_value = pa.table({"x": [1]})
130+
fake_engine._connector = MagicMock()
131+
properties = {"session_user_id": "'u_42'"}
132+
133+
toolkit = WrenToolkit.from_project(tmp_project)
134+
135+
with patch("wren_pydantic._toolkit.WrenEngine", return_value=fake_engine):
136+
toolkit.query("SELECT * FROM orders", limit=5, properties=properties)
137+
138+
fake_engine.query.assert_called_once_with(
139+
"SELECT * FROM orders", limit=5, properties=properties
140+
)
141+
142+
143+
def test_dry_plan_forwards_session_properties(tmp_project, fake_active_profile):
144+
"""dry_plan forwards properties: RLAC predicates are injected while planning."""
145+
fake_engine = MagicMock(name="engine")
146+
fake_engine.dry_plan.return_value = "SELECT 1"
147+
fake_engine._connector = MagicMock()
148+
properties = {"session_user_id": "'u_42'"}
149+
150+
toolkit = WrenToolkit.from_project(tmp_project)
151+
152+
with patch("wren_pydantic._toolkit.WrenEngine", return_value=fake_engine):
153+
toolkit.dry_plan("SELECT * FROM orders", properties=properties)
154+
155+
fake_engine.dry_plan.assert_called_once_with(
156+
"SELECT * FROM orders", properties=properties
157+
)
158+
159+
160+
def test_dry_run_forwards_session_properties(tmp_project, fake_active_profile):
161+
"""dry_run forwards properties so validation matches what query will run."""
162+
fake_engine = MagicMock(name="engine")
163+
fake_engine._connector = MagicMock()
164+
properties = {"session_user_id": "'u_42'"}
165+
166+
toolkit = WrenToolkit.from_project(tmp_project)
167+
168+
with patch("wren_pydantic._toolkit.WrenEngine", return_value=fake_engine):
169+
toolkit.dry_run("SELECT * FROM orders", properties=properties)
170+
171+
fake_engine.dry_run.assert_called_once_with(
172+
"SELECT * FROM orders", properties=properties
173+
)

0 commit comments

Comments
 (0)