You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/09/28 07:28:22 UTC
(superset) branch chore/nightly-python-3.13-canary updated: fix: make MCP tool-description truncation dedent before cutting
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch chore/nightly-python-3.13-canary
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/chore/nightly-python-3.13-canary by this push:
new 24bc3985989 fix: make MCP tool-description truncation dedent before cutting
24bc3985989 is described below
commit 24bc3985989f8a4bc5260da12656780ee0602307
Author: Evan Rusackas <ev...@rusackas.com>
AuthorDate: Mon Sep 28 00:27:41 2026 -0700
fix: make MCP tool-description truncation dedent before cutting
The nightly Python-3.13 canary (from the previous commit) surfaced two real
findings:
- gevent (pulled in transitively, no direct version pin) resolved to
zope-interface==5.4.0, whose metaclass treats every class attribute as an
interface member -- including `__firstlineno__`, the implicit attribute
Python 3.13 (PEP 626 byte-code line tracking) adds to every class. That
raised `zope.interface.exceptions.InvalidInterface` on import under 3.13,
breaking anything that imports gevent (including the gunicorn+gevent
worker class). Floored to zope.interface>=7.2, the first release with
explicit handling for it, and regenerated requirements/development.txt via
scripts/uv-pip-compile.sh. requirements/base.txt is untouched: gevent only
ever comes in through development.in's `gevent` extra.
- `test_tool_inventory_size[update_dashboard]` failed on 3.13 only: Python
3.13's compiler now strips a docstring's common leading whitespace at
compile time (`__doc__` comes out pre-dedented), while 3.11/3.12 store it
raw. `_truncate_description`'s fixed 300-char cutoff was landing at a
different point in `update_dashboard`'s multi-line docstring depending on
which Python compiled it -- the raw (3.11/3.12) form pads every wrapped
line with the original indentation, so fewer real characters fit before
the cutoff. Calling `inspect.cleandoc()` before truncating makes the cut
point identical on every Python version; verified 3.11 and 3.13 now
produce the exact same 4129-byte serialized schema for this tool (up from
3983 on 3.11 before the fix -- the pre-fix 3.11 output was truncating
mid-budget, not incorrect, just inconsistent with what 3.13 already
produced). Bumped update_dashboard's budget from 4100 to 4200 to match.
Verified via a real (non-alpha) Python 3.13.15 interpreter: full unit-test
suite (18,451 tests) passes clean on both 3.11 and 3.13 with these fixes,
under TZ=UTC. The 12 failures seen before this commit were the two findings
above (13 tests) plus 11 pre-existing local-machine timezone artifacts
(this dev box runs America/Los_Angeles; unrelated to Python 3.13, confirmed
by re-running under TZ=UTC).
Co-Authored-By: Evan Rusackas <ev...@preset.io>
Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
requirements/development.in | 11 +++++++++++
requirements/development.txt | 7 ++++---
superset/mcp_service/server.py | 11 +++++++++++
tests/unit_tests/mcp_service/test_tool_inventory.py | 2 +-
4 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/requirements/development.in b/requirements/development.in
index d75496be099..1982438d35d 100644
--- a/requirements/development.in
+++ b/requirements/development.in
@@ -45,3 +45,14 @@
# DockerContainer plus whatever driver its own extra above already
# provides.
testcontainers[cockroachdb,cratedb,mongodb,mssql,mysql,oracle,postgres,trino]>=4.15.0,<5
+
+# gevent (pulled in via the `gevent` extra above) has no direct version
+# requirement on zope.interface, so the resolver was free to pick the
+# oldest version satisfying gevent's own loose constraint: 5.4.0. That
+# version's metaclass treats every class attribute as an interface member,
+# including `__firstlineno__`, the implicit attribute Python 3.13 (PEP 626
+# byte-code line tracking) adds to every class -- so importing gevent
+# (and anything that imports it, e.g. the gunicorn+gevent worker class)
+# raises `zope.interface.exceptions.InvalidInterface` outright on 3.13.
+# 7.2 is the first release with explicit handling for it.
+zope.interface>=7.2
diff --git a/requirements/development.txt b/requirements/development.txt
index 2b7d633809e..27aa54acbe4 100644
--- a/requirements/development.txt
+++ b/requirements/development.txt
@@ -1021,7 +1021,6 @@ setuptools==84.0.0
# pandas-gbq
# pydata-google-auth
# zope-event
- # zope-interface
shillelagh==1.4.5
# via
# -c requirements/base-constraint.txt
@@ -1272,7 +1271,9 @@ zipp==3.23.0
# via importlib-metadata
zope-event==5.0
# via gevent
-zope-interface==5.4.0
- # via gevent
+zope-interface==8.6
+ # via
+ # -r requirements/development.in
+ # gevent
zstandard==0.23.0
# via trino
diff --git a/superset/mcp_service/server.py b/superset/mcp_service/server.py
index 45765560a54..9bda37bd967 100644
--- a/superset/mcp_service/server.py
+++ b/superset/mcp_service/server.py
@@ -22,6 +22,7 @@ Supports both single-pod (in-memory) and multi-pod (Redis) deployments.
For multi-pod deployments, configure MCP_EVENT_STORE_CONFIG with Redis URL.
"""
+import inspect
import logging
import os
from collections.abc import Sequence
@@ -304,7 +305,17 @@ def _truncate_description(text: str, max_length: int) -> str:
Cuts at the last sentence boundary before *max_length*, or at
*max_length* with an ellipsis if no sentence boundary is found.
+
+ Dedents first: Python 3.13 has the compiler strip a docstring's common
+ leading whitespace at compile time (``__doc__`` comes out already
+ cleaned), while 3.11/3.12 store it raw and leave that to the caller. A
+ multi-line tool docstring's raw, un-dedented form is longer per line, so
+ the same character budget lands at a different point in the text
+ depending on which Python compiled it. Cleaning here first makes the cut
+ point (and this function's callers' byte budgets) consistent regardless
+ of interpreter version.
"""
+ text = inspect.cleandoc(text) if text else text
if not text or len(text) <= max_length:
return text
# Try to cut at the last sentence boundary
diff --git a/tests/unit_tests/mcp_service/test_tool_inventory.py b/tests/unit_tests/mcp_service/test_tool_inventory.py
index a723344298e..d3c7cad770d 100644
--- a/tests/unit_tests/mcp_service/test_tool_inventory.py
+++ b/tests/unit_tests/mcp_service/test_tool_inventory.py
@@ -108,7 +108,7 @@ TOOL_BUDGETS = {
"save_sql_query": 1_600,
"update_chart": 55_000,
"update_chart_preview": 55_000,
- "update_dashboard": 4_100,
+ "update_dashboard": 4_200,
"update_dataset_metric": 3_100,
}