Skip to content

Commit 6884ce9

Browse files
fix(evaluators): use SQLGlot native extra without direct sqlglotc pin (#243)
## Summary - Upgrade the builtin SQL evaluator dependency to `sqlglot[c]>=30.11.0,<30.12.0`. - Remove the direct `sqlglotc` dependency so SQLGlot owns the matching native runtime package. - Update CrewAI examples to use the same dependency contract. - Add a regression test for SQLGlot public imports and SQL evaluator construction. - Adapt SQL evaluator typing to SQLGlot 30 stubs without changing runtime behavior. ## Context `sqlglotc` contributes native modules under the public `sqlglot` package namespace. Keeping both `sqlglot[c]` and a separate direct `sqlglotc` pin works in normal pip-style installs, but it is fragile for build systems that model wheels separately. This keeps the public dependency contract on `sqlglot[c]` and lets SQLGlot select the matching native package. This keeps Agent Control installable as a generic OSS package while preserving the low-latency SQL evaluator path for environments that install SQLGlot with native extensions. ## Validation - `UV_NO_CONFIG=1 UV_DEFAULT_INDEX=https://pypi.org/simple make sync` - `UV_NO_CONFIG=1 UV_DEFAULT_INDEX=https://pypi.org/simple uv run --package agent-control-evaluators pytest evaluators/builtin/tests/sql/test_sqlglot_runtime.py -q` - `UV_NO_CONFIG=1 UV_DEFAULT_INDEX=https://pypi.org/simple uv run --package agent-control-evaluators pytest evaluators/builtin/tests/sql -q` - `UV_NO_CONFIG=1 UV_DEFAULT_INDEX=https://pypi.org/simple make evaluators-test` - `UV_NO_CONFIG=1 UV_DEFAULT_INDEX=https://pypi.org/simple make evaluators-lint` - `UV_NO_CONFIG=1 UV_DEFAULT_INDEX=https://pypi.org/simple make evaluators-typecheck` - `git diff --check`
1 parent 6df65f4 commit 6884ce9

7 files changed

Lines changed: 34 additions & 19 deletions

File tree

evaluators/builtin/pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ dependencies = [
1111
"pydantic>=2.12.4",
1212
"google-re2>=1.1",
1313
"jsonschema>=4.0.0",
14-
"sqlglot[c]>=29.0.0,<29.1.0",
15-
"sqlglotc>=29.0.0,<29.1.0",
14+
"sqlglot[c]>=30.11.0,<30.12.0",
1615
]
1716

1817
[project.optional-dependencies]

evaluators/builtin/src/agent_control_evaluators/sql/evaluator.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import hashlib
88
import logging
99
from dataclasses import dataclass, field
10-
from typing import Any
10+
from typing import Any, cast
1111

1212
import sqlglot
1313
from agent_control_models import EvaluatorResult
@@ -239,7 +239,8 @@ def _analyze_query_structure(self, stmt: exp.Expression) -> QueryAnalysis:
239239
top_level_where = top_level_select.find(exp.Where) if top_level_select else None
240240

241241
# Single tree walk to collect all metrics
242-
for node in stmt.walk():
242+
for raw_node in stmt.walk():
243+
node = cast(exp.Expression, raw_node)
243244
# Collect operations
244245
op_name = self._get_operation_name(node)
245246
if op_name:
@@ -310,7 +311,7 @@ def _is_in_node_but_not_in_subqueries(
310311
# If we hit a SELECT node before the target, node is in a subquery
311312
if isinstance(current, exp.Select) and current is not target_node:
312313
return False
313-
current = current.parent
314+
current = cast(exp.Expression | None, current.parent)
314315
return False
315316

316317
def _is_in_where_clause(self, column: exp.Column) -> bool:
@@ -319,7 +320,7 @@ def _is_in_where_clause(self, column: exp.Column) -> bool:
319320
while current:
320321
if isinstance(current, exp.Where):
321322
return True
322-
current = current.parent
323+
current = cast(exp.Expression | None, current.parent)
323324
return False
324325

325326
def _is_in_top_level_select(
@@ -339,20 +340,20 @@ def _is_in_top_level_select(
339340
# If we hit another SELECT before the top-level, we're in a subquery
340341
if isinstance(current, exp.Select) and current is not top_level_select:
341342
return False
342-
current = current.parent
343+
current = cast(exp.Expression | None, current.parent)
343344
return False
344345

345346
def _is_in_select_clause(self, column: exp.Column) -> bool:
346347
"""Check if column is in any SELECT clause (including subqueries)."""
347-
current: exp.Expression | None = column.parent
348+
current = cast(exp.Expression | None, column.parent)
348349
while current:
349350
# Check if this column is in a SELECT's expressions
350351
if isinstance(current, exp.Select):
351352
for select_expr in current.expressions:
352353
if self._is_descendant_of(column, select_expr):
353354
return True
354355
return False
355-
current = current.parent
356+
current = cast(exp.Expression | None, current.parent)
356357
return False
357358

358359
def _is_descendant_of(
@@ -363,7 +364,7 @@ def _is_descendant_of(
363364
while current:
364365
if current is potential_ancestor:
365366
return True
366-
current = current.parent
367+
current = cast(exp.Expression | None, current.parent)
367368
return False
368369

369370
def _is_top_level_select(self, select_node: exp.Select) -> bool:
@@ -1106,7 +1107,9 @@ def _evaluate_sync(self, data: Any) -> EvaluatorResult:
11061107
metadata=self._create_query_metadata(query),
11071108
)
11081109

1109-
parsed_statements = [stmt for stmt in parsed if stmt is not None]
1110+
parsed_statements = [
1111+
cast(exp.Expression, stmt) for stmt in parsed if stmt is not None
1112+
]
11101113

11111114
# 2. Check multi-statements
11121115
if not self.config.allow_multi_statements or self.config.max_statements:
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""SQLGlot runtime integration tests."""
2+
3+
from sqlglot import exp
4+
5+
from agent_control_evaluators.sql import SQLEvaluator, SQLEvaluatorConfig
6+
7+
8+
def test_sqlglot_public_imports_support_sql_evaluator():
9+
"""SQLGlot's public API should remain importable with the native extra installed."""
10+
# Given: the SQL evaluator package imports SQLGlot's public expression module
11+
assert exp.Select is not None
12+
13+
# When: constructing the SQL evaluator
14+
evaluator = SQLEvaluator(SQLEvaluatorConfig(blocked_operations=["DROP"]))
15+
16+
# Then: the evaluator can be created without SQLGlot import shadowing failures
17+
assert evaluator.metadata.name == "sql"

examples/crewai/content_publishing_flow/pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ dependencies = [
77
"crewai[tools]>=1.10.1",
88
"openai>=1.0.0",
99
"python-dotenv>=1.0.0",
10-
"sqlglot[c]>=29.0.0,<29.1.0",
11-
"sqlglotc>=29.0.0,<29.1.0",
10+
"sqlglot[c]>=30.11.0,<30.12.0",
1211
]
1312

1413
[project.scripts]

examples/crewai/evaluator_showcase/pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ dependencies = [
77
"crewai[tools]>=1.10.1",
88
"openai>=1.0.0",
99
"python-dotenv>=1.0.0",
10-
"sqlglot[c]>=29.0.0,<29.1.0",
11-
"sqlglotc>=29.0.0,<29.1.0",
10+
"sqlglot[c]>=30.11.0,<30.12.0",
1211
]
1312

1413
[project.optional-dependencies]

examples/crewai/secure_research_crew/pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ dependencies = [
77
"crewai[tools]>=1.10.1",
88
"openai>=1.0.0",
99
"python-dotenv>=1.0.0",
10-
"sqlglot[c]>=29.0.0,<29.1.0",
11-
"sqlglotc>=29.0.0,<29.1.0",
10+
"sqlglot[c]>=30.11.0,<30.12.0",
1211
]
1312

1413
[project.scripts]

examples/crewai/steering_financial_agent/pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ dependencies = [
77
"crewai[tools]>=1.10.1",
88
"openai>=1.0.0",
99
"python-dotenv>=1.0.0",
10-
"sqlglot[c]>=29.0.0,<29.1.0",
11-
"sqlglotc>=29.0.0,<29.1.0",
10+
"sqlglot[c]>=30.11.0,<30.12.0",
1211
]
1312

1413
[project.scripts]

0 commit comments

Comments
 (0)