Source: textual
Version: 2.1.2-1
Severity: serious
Tags: ftbfs, patch

rich 13.9.4-1.1 causes one test failure in textual:

__________________________________ test_demo ___________________________________

    async def test_demo():
        """Test the demo runs."""
        # Test he demo can at least run.
        # This exists mainly to catch screw-ups that might effect only certain 
Python versions.
        app = DemoApp()
>       async with app.run_test() as pilot:
                   ^^^^^^^^^^^^^^

tests/test_demo.py:9:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/lib/python3.13/contextlib.py:221: in __aexit__
    await anext(self.gen)
/usr/lib/python3/dist-packages/textual/app.py:1991: in run_test
    raise self._exception
/usr/lib/python3/dist-packages/textual/message_pump.py:609: in 
_process_messages_loop
    await self._dispatch_message(message)
/usr/lib/python3/dist-packages/textual/message_pump.py:673: in _dispatch_message
    await self.on_event(message)
/usr/lib/python3/dist-packages/textual/message_pump.py:754: in on_event
    await self._on_message(event)
/usr/lib/python3/dist-packages/textual/message_pump.py:775: in _on_message
    await invoke(method, message)
/usr/lib/python3/dist-packages/textual/_callback.py:93: in invoke
    return await _invoke(callback, *params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3/dist-packages/textual/_callback.py:53: in _invoke
    result = callback(*params[:parameter_count])
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3/dist-packages/textual/screen.py:1335: in _on_screen_resume
    self._refresh_layout(size)
/usr/lib/python3/dist-packages/textual/screen.py:1255: in _refresh_layout
    self._compositor_refresh()
/usr/lib/python3/dist-packages/textual/screen.py:1091: in _compositor_refresh
    update = self._compositor.render_update(
/usr/lib/python3/dist-packages/textual/_compositor.py:1082: in render_update
    return self.render_full_update(simplify=simplify)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3/dist-packages/textual/_compositor.py:1118: in 
render_full_update
    chops = self._render_chops(crop, lambda y: True)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3/dist-packages/textual/_compositor.py:1188: in _render_chops
    for region, clip, strips in renders:
                                ^^^^^^^
/usr/lib/python3/dist-packages/textual/_compositor.py:1037: in _get_renders
    widget.render_lines(
/usr/lib/python3/dist-packages/textual/widget.py:3919: in render_lines
    strips = self._styles_cache.render_widget(self, crop)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3/dist-packages/textual/_styles_cache.py:127: in render_widget
    strips = self.render(
/usr/lib/python3/dist-packages/textual/_styles_cache.py:230: in render
    strip = render_line(
/usr/lib/python3/dist-packages/textual/_styles_cache.py:449: in render_line
    line = render_content_line(y - gutter.top)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3/dist-packages/textual/widget.py:3902: in render_line
    self._render_content()
/usr/lib/python3/dist-packages/textual/widget.py:3888: in _render_content
    strips = Visual.to_strips(self, visual, width, height, self.visual_style)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3/dist-packages/textual/visual.py:199: in to_strips
    strips = visual.render_strips(
/usr/lib/python3/dist-packages/textual/content.py:520: in render_strips
    strip_lines = [Strip(*line.to_strip(style)) for line in lines]
                          ^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3/dist-packages/textual/content.py:1376: in to_strip
    _Segment(text, (style + text_style).rich_style_with_offset(x, y))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3/dist-packages/textual/style.py:365: in rich_style_with_offset
    meta={**self.meta, "offset": (x, y)},
            ^^^^^^^^^
/usr/lib/python3.13/functools.py:1025: in __get__
    val = self.func(instance)
          ^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <[ValueError('bad marshal data (unknown type code)') raised in repr()] 
Style object at 0x7fa0743a8050>

    @cached_property
    def meta(self) -> Mapping[str, Any]:
        """Get meta information (can not be changed after construction)."""
>       return {} if self._meta is None else loads(self._meta)
                                             ^^^^^^^^^^^^^^^^^
E       ValueError: bad marshal data (unknown type code)

/usr/lib/python3/dist-packages/textual/style.py:396: ValueError


This was my NMU, so I'm sorry for not spotting the problem in advance; I 
thought the rich changes were small enough to avoid compatibility issues 
in textual and missed the fact that they switched from marshal to pickle 
for style meta information.  However, I don't think the compatibility 
breakage could really have been avoided anyway: the change that caused 
this was part of upstream changes to support Python 3.14, and when I 
experimented locally I also couldn't find a way to get marshal.dumps to 
do the right thing on Python 3.14 so I can absolutely see why they 
switched to pickle instead.

The corresponding upstream fix to textual is in a commit unhelpfully 
labelled just "optimization" 
(https://github.com/Textualize/textual/pull/6169); as far as I can see 
upstream failed to label either side of this as affecting compatibility 
between rich and textual.  I think the only sensible way forward is to 
cherry-pick the relevant part of the textual change as well, add a 
Depends to textual for the new rich, and add a Breaks on old textual 
versions to rich.

https://salsa.debian.org/morph/textual/-/merge_requests/1 has the 
necessary changes to textual, and I've attached a debdiff here too.  The 
change to rich would then just be adding "Breaks: python3-textual (<< 
2.1.2-1.1~)".

Thanks,

-- 
Colin Watson (he/him)                              [[email protected]]
diff -Nru textual-2.1.2/debian/changelog textual-2.1.2/debian/changelog
--- textual-2.1.2/debian/changelog      2025-03-16 21:27:22.000000000 +0000
+++ textual-2.1.2/debian/changelog      2026-01-12 11:18:59.000000000 +0000
@@ -1,3 +1,10 @@
+textual (2.1.2-1.1) UNRELEASED; urgency=medium
+
+  * Non-maintainer upload.
+  * Use pickle for style meta information to match rich 13.9.4-1.1.
+
+ -- Colin Watson <[email protected]>  Mon, 12 Jan 2026 11:18:59 +0000
+
 textual (2.1.2-1) unstable; urgency=medium
 
   * New upstream release
diff -Nru textual-2.1.2/debian/control textual-2.1.2/debian/control
--- textual-2.1.2/debian/control        2025-03-16 21:27:22.000000000 +0000
+++ textual-2.1.2/debian/control        2026-01-12 11:18:59.000000000 +0000
@@ -12,7 +12,7 @@
                python3-pytest (>= 6.2.3) <!nocheck>,
                python3-pytest-asyncio <!nocheck>,
                python3-pytest-xdist <!nocheck>,
-               python3-rich (>= 10.7.0) <!nocheck>,
+               python3-rich (>= 13.9.4-1.1~) <!nocheck>,
                python3-syrupy <!nocheck>,
                python3-typing-extensions <!nocheck>,
 Standards-Version: 4.6.2
diff -Nru textual-2.1.2/debian/patches/pickle-style-meta.patch 
textual-2.1.2/debian/patches/pickle-style-meta.patch
--- textual-2.1.2/debian/patches/pickle-style-meta.patch        1970-01-01 
01:00:00.000000000 +0100
+++ textual-2.1.2/debian/patches/pickle-style-meta.patch        2026-01-12 
11:18:59.000000000 +0000
@@ -0,0 +1,25 @@
+From: Colin Watson <[email protected]>
+Date: Mon, 12 Jan 2026 11:18:22 +0000
+Subject: Use pickle for style meta information
+
+Backported from https://github.com/Textualize/textual/pull/6169; needed
+to match rich 14.2.0 / 13.9.4-1.1.
+
+Last-Update: 2026-01-12
+---
+ src/textual/style.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/textual/style.py b/src/textual/style.py
+index c96c9b7..7df24da 100644
+--- a/src/textual/style.py
++++ b/src/textual/style.py
+@@ -9,7 +9,7 @@ from __future__ import annotations
+ 
+ from dataclasses import dataclass
+ from functools import cached_property, lru_cache
+-from marshal import dumps, loads
++from pickle import dumps, loads
+ from typing import TYPE_CHECKING, Any, Iterable, Mapping
+ 
+ import rich.repr
diff -Nru textual-2.1.2/debian/patches/series 
textual-2.1.2/debian/patches/series
--- textual-2.1.2/debian/patches/series 1970-01-01 01:00:00.000000000 +0100
+++ textual-2.1.2/debian/patches/series 2026-01-12 11:18:29.000000000 +0000
@@ -0,0 +1 @@
+pickle-style-meta.patch

Reply via email to