Package: urwid
Version: 2.6.4-1
Severity: normal
Tags: patch
User: ubuntu-de...@lists.ubuntu.com
Usertags: origin-ubuntu noble ubuntu-patch

Dear Maintainer,

With the version of urwid 2.6.4-1 currently in trixie, the following
code fails with an exception:

```python
 from urwid import Pile
 
 Pile([
     ("pack", Pile([])),
 ]).render((10,))
 ```

  File "/usr/lib/python3/dist-packages/urwid/widget/widget.py", line 112, in 
cached_render
    canv = fn(self, size, focus=focus)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/urwid/widget/pile.py", line 822, in 
render
    _widths, heights, size_args = self.get_rows_sizes(size, focus)
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/urwid/widget/pile.py", line 730, in 
get_rows_sizes
    heights.append(w.pack(w_h_arg, item_focus)[1])
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/urwid/widget/pile.py", line 744, in pack
    return super().pack(size, focus)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/urwid/widget/widget.py", line 401, in 
pack
    raise WidgetError(f"Cannot pack (maxcol,) size, this is not a flow widget: 
{self!r}")
urwid.widget.widget.WidgetError: Cannot pack (maxcol,) size, this is not a flow 
widget: <Pile widget>

The same code used to work with urwid 2.1.2-4 in bookworm.

I applied a fix from upstream [1] that was included in urwid 2.6.5. I
think the proper way forward would be to take a more recent upstream
version of urwid. That said, we are in feature freeze downstream in
Ubuntu so I skipped the refactoring bits.

In Ubuntu, the attached patch was applied to achieve the following:

  * Apply upstream patch to fix a crash when rendering an empty Pile or an
    empty Columns as a flow widget. (LP: #2058388)
    + d/patches/fix-crash-empty-pile.patch


Thanks for considering the patch.


-- System Information:
Debian Release: trixie/sid
  APT prefers noble
  APT policy: (500, 'noble'), (100, 'noble-proposed')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 6.8.0-11-generic (SMP w/8 CPU threads; PREEMPT)
Kernel taint flags: TAINT_PROPRIETARY_MODULE, TAINT_OOT_MODULE
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8), LANGUAGE not set
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

[1] 
https://github.com/urwid/urwid/commit/83c278b53de431a9b41d7ddadf5f318914246593
diff -Nru urwid-2.6.4/debian/patches/fix-crash-empty-pile.patch 
urwid-2.6.4/debian/patches/fix-crash-empty-pile.patch
--- urwid-2.6.4/debian/patches/fix-crash-empty-pile.patch       1970-01-01 
01:00:00.000000000 +0100
+++ urwid-2.6.4/debian/patches/fix-crash-empty-pile.patch       2024-03-19 
14:23:31.000000000 +0100
@@ -0,0 +1,158 @@
+Description: Fix crash when rendering empty Pile or Columns as a flow widget
+ Special case: in case of `Columns`/`Pile` empty - use fallback sizing (#843)
+ .
+ * Extend `repr` to provide brief info about contents
+ .
+Author: Aleksei Stepanov <aleks...@nvidia.com>
+Origin: upstream, 
https://github.com/urwid/urwid/commit/83c278b53de431a9b41d7ddadf5f318914246593
+Bug-Ubuntu: https://launchpad.net/bugs/2058388
+Last-Update: 2024-03-19
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+diff --git a/urwid/widget/columns.py b/urwid/widget/columns.py
+index e3fa134..5a3421f 100644
+--- a/urwid/widget/columns.py
++++ b/urwid/widget/columns.py
+@@ -66,40 +66,47 @@ class Columns(Widget, WidgetContainerMixin, 
WidgetContainerListContentsMixin):
+ 
+         # BOX-only widget
+         >>> Columns((SolidFill("#"),))
+-        <Columns box widget>
++        <Columns box widget with 1 item>
+ 
+         # BOX-only widget with "get height from max"
+         >>> Columns((SolidFill("#"),), box_columns=(0,))
+-        <Columns box widget>
++        <Columns box widget with 1 item>
+ 
+         # FLOW-only
+         >>> Columns((Edit(),))
+-        <Columns selectable flow widget>
++        <Columns selectable flow widget with 1 item>
+ 
+         # FLOW allowed by "box_columns"
+         >>> Columns((Edit(), SolidFill("#")), box_columns=(1,))
+-        <Columns selectable flow widget>
++        <Columns selectable flow widget with 2 items>
+ 
+         # FLOW/FIXED
+         >>> Columns((Text("T"),))
+-        <Columns fixed/flow widget>
++        <Columns fixed/flow widget with 1 item>
+ 
+         # GIVEN BOX only -> BOX only
+         >>> Columns(((5, SolidFill("#")),), box_columns=(0,))
+-        <Columns box widget>
++        <Columns box widget with 1 item>
+ 
+         # No FLOW - BOX only
+         >>> Columns(((5, SolidFill("#")), SolidFill("*")), box_columns=(0, 1))
+-        <Columns box widget>
++        <Columns box widget with 2 items>
+ 
+         # FIXED only -> FIXED
+         >>> Columns(((WHSettings.PACK, BigText("1", font)),))
+-        <Columns fixed widget>
++        <Columns fixed widget with 1 item>
+ 
+         # Invalid sizing combination -> use fallback settings (and produce 
warning)
+         >>> Columns(((WHSettings.PACK, SolidFill("#")),))
+-        <Columns box/flow widget>
++        <Columns box/flow widget with 1 item>
++
++        # Special case: empty columns widget sizing is impossible to calculate
++        >>> Columns(())
++        <Columns box/flow widget without contents>
+         """
++        if not self.contents:
++            return frozenset((urwid.BOX, urwid.FLOW))
++
+         strict_box = False
+         has_flow = False
+ 
+@@ -280,6 +287,13 @@ class Columns(Widget, WidgetContainerMixin, 
WidgetContainerListContentsMixin):
+         self.min_width = min_width
+         self._cache_maxcol = None
+ 
++    def _repr_words(self) -> list[str]:
++        if len(self.contents) > 1:
++            return [*super()._repr_words(), f"with {len(self.contents)} 
items"]
++        if self.contents:
++            return [*super()._repr_words(), "with 1 item"]
++        return [*super()._repr_words(), "without contents"]
++
+     def _contents_modified(self) -> None:
+         """
+         Recalculate whether this widget should be selectable whenever the
+diff --git a/urwid/widget/pile.py b/urwid/widget/pile.py
+index 2ee27fb..a1fc37f 100644
+--- a/urwid/widget/pile.py
++++ b/urwid/widget/pile.py
+@@ -58,36 +58,42 @@ class Pile(Widget, WidgetContainerMixin, 
WidgetContainerListContentsMixin):
+ 
+         # BOX-only widget
+         >>> Pile((SolidFill("#"),))
+-        <Pile box widget>
++        <Pile box widget with 1 item>
+ 
+         # GIVEN BOX -> BOX/FLOW
+         >>> Pile(((10, SolidFill("#")),))
+-        <Pile box/flow widget>
++        <Pile box/flow widget with 1 item>
+ 
+         # FLOW-only
+         >>> Pile((ProgressBar(None, None),))
+-        <Pile flow widget>
++        <Pile flow widget with 1 item>
+ 
+         # FIXED -> FIXED
+         >>> Pile(((WHSettings.PACK, BigText("0", font)),))
+-        <Pile fixed widget>
++        <Pile fixed widget with 1 item>
+ 
+         # FLOW/FIXED -> FLOW/FIXED
+         >>> Pile(((WHSettings.PACK, Text("text")),))
+-        <Pile fixed/flow widget>
++        <Pile fixed/flow widget with 1 item>
+ 
+         # FLOW + FIXED widgets -> FLOW/FIXED
+         >>> Pile((ProgressBar(None, None), (WHSettings.PACK, BigText("0", 
font))))
+-        <Pile fixed/flow widget>
++        <Pile fixed/flow widget with 2 items>
+ 
+         # GIVEN BOX + FIXED widgets -> BOX/FLOW/FIXED (GIVEN BOX allows 
overriding its height & allows any width)
+         >>> Pile(((10, SolidFill("#")), (WHSettings.PACK, BigText("0", 
font))))
+-        <Pile widget>
++        <Pile widget with 2 items>
+ 
+         # Invalid sizing combination -> use fallback settings (and produce 
warning)
+         >>> Pile(((WHSettings.WEIGHT, 1, BigText("0", font)),))
+-        <Pile box/flow widget>
++        <Pile box/flow widget with 1 item>
++
++        # Special case: empty pile widget sizing is impossible to calculate
++        >>> Pile(())
++        <Pile box/flow widget without contents>
+         """
++        if not self.contents:
++            return frozenset((Sizing.BOX, Sizing.FLOW))
+         strict_box = False
+         has_flow = False
+ 
+@@ -225,6 +231,13 @@ class Pile(Widget, WidgetContainerMixin, 
WidgetContainerListContentsMixin):
+ 
+         self.pref_col = 0
+ 
++    def _repr_words(self) -> list[str]:
++        if len(self.contents) > 1:
++            return [*super()._repr_words(), f"with {len(self.contents)} 
items"]
++        if self.contents:
++            return [*super()._repr_words(), "with 1 item"]
++        return [*super()._repr_words(), "without contents"]
++
+     def _contents_modified(self) -> None:
+         """Recalculate whether this widget should be selectable whenever the 
contents has been changed."""
+         self._selectable = any(w.selectable() for w, o in self.contents)
+-- 
+2.43.0
+
diff -Nru urwid-2.6.4/debian/patches/series urwid-2.6.4/debian/patches/series
--- urwid-2.6.4/debian/patches/series   2024-02-24 20:58:30.000000000 +0100
+++ urwid-2.6.4/debian/patches/series   2024-03-19 14:23:31.000000000 +0100
@@ -1,2 +1,3 @@
 no-sphinx-changelog.diff
 #version-module.diff
+fix-crash-empty-pile.patch

Reply via email to