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,
 }