urwid 2.6.9__py3-none-any.whl → 2.6.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
urwid/version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '2.6.9'
16
- __version_tuple__ = version_tuple = (2, 6, 9)
15
+ __version__ = version = '2.6.11'
16
+ __version_tuple__ = version_tuple = (2, 6, 11)
urwid/widget/listbox.py CHANGED
@@ -20,6 +20,7 @@
20
20
 
21
21
  from __future__ import annotations
22
22
 
23
+ import operator
23
24
  import typing
24
25
  import warnings
25
26
  from collections.abc import Iterable, Sized
@@ -77,6 +78,18 @@ class ScrollSupportingBody(Protocol):
77
78
  def get_prev(self, position: _K) -> tuple[Widget, _K] | tuple[None, None]: ...
78
79
 
79
80
 
81
+ @runtime_checkable
82
+ class EstimatedSized(Protocol):
83
+ """Widget can estimate it's size.
84
+
85
+ PEP 424 defines API for memory-efficiency.
86
+ For the ListBox it's a sign of the limited body length.
87
+ The main use-case is lazy-load, where real length calculation is expensive.
88
+ """
89
+
90
+ def __length_hint__(self) -> int: ...
91
+
92
+
80
93
  class ListWalker(metaclass=signals.MetaSignals): # pylint: disable=no-member, unsubscriptable-object
81
94
  # mixin not named as mixin
82
95
  signals: typing.ClassVar[list[str]] = ["modified"]
@@ -431,11 +444,18 @@ class ListBox(Widget, WidgetContainerMixin):
431
444
  )
432
445
  self.body = body
433
446
 
434
- def __len__(self) -> int:
447
+ @property
448
+ def __len__(self) -> Callable[[], int]:
435
449
  if isinstance(self._body, Sized):
436
- return len(self._body)
450
+ return self._body.__len__
437
451
  raise AttributeError(f"{self._body.__class__.__name__} is not Sized")
438
452
 
453
+ @property
454
+ def __length_hint__(self) -> Callable[[], int]: # pylint: disable=invalid-length-hint-returned
455
+ if isinstance(self._body, (Sized, EstimatedSized)):
456
+ return lambda: operator.length_hint(self._body)
457
+ raise AttributeError(f'{self._body.__class__.__name__} is not Sized and do not implement "__length_hint__"')
458
+
439
459
  def calculate_visible(
440
460
  self,
441
461
  size: tuple[int, int],
@@ -577,9 +597,9 @@ class ListBox(Widget, WidgetContainerMixin):
577
597
  if not isinstance(self._body, ScrollSupportingBody):
578
598
  raise ListBoxError(f"{self} body do not implement methods required for scrolling protocol")
579
599
 
580
- if not isinstance(self._body, (Sized, TreeWalker)):
600
+ if not isinstance(self._body, (Sized, EstimatedSized, TreeWalker)):
581
601
  raise ListBoxError(
582
- f"{self} body is not a Sized and not a TreeWalker."
602
+ f"{self} body is not a Sized, can not estimate it's size and not a TreeWalker."
583
603
  f"Scroll is not allowed due to risk of infinite cycle of widgets load."
584
604
  )
585
605
 
@@ -643,7 +663,7 @@ class ListBox(Widget, WidgetContainerMixin):
643
663
 
644
664
  def require_relative_scroll(self, size: tuple[int, int], focus: bool = False) -> bool:
645
665
  """Widget require relative scroll due to performance limitations of real lines count calculation."""
646
- return isinstance(self._body, Sized) and (size[1] * 3 < len(self))
666
+ return isinstance(self._body, (Sized, EstimatedSized)) and (size[1] * 3 < operator.length_hint(self.body))
647
667
 
648
668
  def get_first_visible_pos(self, size: tuple[int, int], focus: bool = False) -> int:
649
669
  self._check_support_scrolling()
@@ -24,9 +24,9 @@ import typing
24
24
  import warnings
25
25
 
26
26
  if typing.TYPE_CHECKING:
27
- from collections.abc import Callable, Collection, Iterator
27
+ from collections.abc import Callable, Collection, Iterable, Iterator
28
28
 
29
- from typing_extensions import Concatenate, ParamSpec
29
+ from typing_extensions import Concatenate, ParamSpec, Self
30
30
 
31
31
  ArgSpec = ParamSpec("ArgSpec")
32
32
  Ret = typing.TypeVar("Ret")
@@ -88,23 +88,69 @@ class MonitoredList(typing.List[_T], typing.Generic[_T]):
88
88
  for item in self:
89
89
  yield None, item
90
90
 
91
- __add__ = _call_modified(list.__add__) # type: ignore[assignment] # magic like old __super__
92
- __delitem__ = _call_modified(list.__delitem__) # type: ignore[assignment] # magic like old __super__
93
-
94
- __iadd__ = _call_modified(list.__iadd__) # type: ignore[assignment] # magic like old __super__
95
- __imul__ = _call_modified(list.__imul__) # type: ignore[assignment] # magic like old __super__
96
- __rmul__ = _call_modified(list.__rmul__) # type: ignore[assignment] # magic like old __super__
97
- __setitem__ = _call_modified(list.__setitem__) # type: ignore[assignment] # magic like old __super__
98
-
99
- append = _call_modified(list.append) # type: ignore[assignment] # magic like old __super__
100
- extend = _call_modified(list.extend) # type: ignore[assignment] # magic like old __super__
101
- insert = _call_modified(list.insert) # type: ignore[assignment] # magic like old __super__
102
- pop = _call_modified(list.pop) # type: ignore[assignment] # magic like old __super__
103
- remove = _call_modified(list.remove) # type: ignore[assignment] # magic like old __super__
104
- reverse = _call_modified(list.reverse) # type: ignore[assignment] # magic like old __super__
105
- sort = _call_modified(list.sort) # type: ignore[assignment] # magic like old __super__
106
- if hasattr(list, "clear"):
107
- clear = _call_modified(list.clear) # type: ignore[assignment] # magic like old __super__
91
+ @_call_modified
92
+ def __add__(self, __value: list[_T]) -> Self:
93
+ return super().__add__(__value)
94
+
95
+ @_call_modified
96
+ def __delitem__(self, __key: typing.SupportsIndex | slice) -> None:
97
+ super().__delitem__(__key)
98
+
99
+ @_call_modified
100
+ def __iadd__(self, __value: Iterable[_T]) -> Self:
101
+ return super().__iadd__(__value)
102
+
103
+ @_call_modified
104
+ def __rmul__(self, __value: typing.SupportsIndex) -> Self:
105
+ return super().__rmul__(__value)
106
+
107
+ @_call_modified
108
+ def __imul__(self, __value: typing.SupportsIndex) -> Self:
109
+ return super().__imul__(__value)
110
+
111
+ @typing.overload
112
+ @_call_modified
113
+ def __setitem__(self, __key: typing.SupportsIndex, __value: _T) -> None: ...
114
+
115
+ @typing.overload
116
+ @_call_modified
117
+ def __setitem__(self, __key: slice, __value: Iterable[_T]) -> None: ...
118
+
119
+ @_call_modified
120
+ def __setitem__(self, __key: typing.SupportsIndex | slice, __value: _T | Iterable[_T]) -> None:
121
+ super().__setitem__(__key, __value)
122
+
123
+ @_call_modified
124
+ def append(self, __object: _T) -> None:
125
+ super().append(__object)
126
+
127
+ @_call_modified
128
+ def extend(self, __iterable: Iterable[_T]) -> None:
129
+ super().extend(__iterable)
130
+
131
+ @_call_modified
132
+ def pop(self, __index: typing.SupportsIndex = -1) -> _T:
133
+ return super().pop(__index)
134
+
135
+ @_call_modified
136
+ def insert(self, __index: typing.SupportsIndex, __object: _T) -> None:
137
+ super().insert(__index, __object)
138
+
139
+ @_call_modified
140
+ def remove(self, __value: _T) -> None:
141
+ super().remove(__value)
142
+
143
+ @_call_modified
144
+ def reverse(self) -> None:
145
+ super().reverse()
146
+
147
+ @_call_modified
148
+ def sort(self, *, key: Callable[[_T], typing.Any] | None = None, reverse: bool = False) -> None:
149
+ super().sort(key=key, reverse=reverse)
150
+
151
+ @_call_modified
152
+ def clear(self) -> None:
153
+ super().clear()
108
154
 
109
155
 
110
156
  class MonitoredFocusList(MonitoredList[_T], typing.Generic[_T]):
urwid/widget/padding.py CHANGED
@@ -40,12 +40,14 @@ class Padding(WidgetDecoration[WrappedWidget], typing.Generic[WrappedWidget]):
40
40
  self,
41
41
  w: WrappedWidget,
42
42
  align: (
43
- Literal["left", "center", "right"] | Align | tuple[Literal["relative", WHSettings.RELATIVE], int]
43
+ Literal["left", "center", "right"]
44
+ | Align
45
+ | tuple[Literal["relative", WHSettings.RELATIVE, "fixed left", "fixed right"], int]
44
46
  ) = Align.LEFT,
45
47
  width: (
46
48
  int
47
49
  | Literal["pack", "clip", WHSettings.PACK, WHSettings.CLIP]
48
- | tuple[Literal["relative", WHSettings.RELATIVE], int]
50
+ | tuple[Literal["relative", WHSettings.RELATIVE, "fixed left", "fixed right"], int]
49
51
  ) = RELATIVE_100,
50
52
  min_width: int | None = None,
51
53
  left: int = 0,
urwid/widget/popup.py CHANGED
@@ -24,7 +24,7 @@ import typing
24
24
 
25
25
  from urwid.canvas import CompositeCanvas
26
26
 
27
- from .constants import Sizing
27
+ from .constants import Align, Sizing, VAlign
28
28
  from .overlay import Overlay
29
29
  from .widget import delegate_to_widget_mixin
30
30
  from .widget_decoration import WidgetDecoration
@@ -86,8 +86,7 @@ class PopUpLauncher(delegate_to_widget_mixin("_original_widget"), WidgetDecorati
86
86
 
87
87
 
88
88
  class PopUpTarget(WidgetDecoration[WrappedWidget]):
89
- # FIXME: this whole class is a terrible hack and must be fixed
90
- # when layout and rendering are separated
89
+ # FIXME: this whole class is a terrible hack and must be fixed when layout and rendering are separated
91
90
  _sizing = frozenset((Sizing.BOX,))
92
91
  _selectable = True
93
92
 
@@ -105,19 +104,23 @@ class PopUpTarget(WidgetDecoration[WrappedWidget]):
105
104
  if self._pop_up != w:
106
105
  self._pop_up = w
107
106
  self._current_widget = Overlay(
108
- w,
109
- self._original_widget,
110
- ("fixed left", left),
111
- overlay_width,
112
- ("fixed top", top),
113
- overlay_height,
107
+ top_w=w,
108
+ bottom_w=self._original_widget,
109
+ align=Align.LEFT,
110
+ width=overlay_width,
111
+ valign=VAlign.TOP,
112
+ height=overlay_height,
113
+ left=left,
114
+ top=top,
114
115
  )
115
116
  else:
116
117
  self._current_widget.set_overlay_parameters(
117
- ("fixed left", left),
118
- overlay_width,
119
- ("fixed top", top),
120
- overlay_height,
118
+ align=Align.LEFT,
119
+ width=overlay_width,
120
+ valign=VAlign.TOP,
121
+ height=overlay_height,
122
+ left=left,
123
+ top=top,
121
124
  )
122
125
  else:
123
126
  self._pop_up = None
@@ -130,8 +130,6 @@ class SupportsScroll(WidgetProto, Protocol):
130
130
  class SupportsRelativeScroll(WidgetProto, Protocol):
131
131
  """Relative scroll-specific methods."""
132
132
 
133
- def __len__(self) -> int: ...
134
-
135
133
  def require_relative_scroll(self, size: tuple[int, int], focus: bool = False) -> bool: ...
136
134
 
137
135
  def get_first_visible_pos(self, size: tuple[int, int], focus: bool = False) -> int: ...
@@ -516,18 +514,32 @@ class ScrollBar(WidgetDecoration[WrappedWidget]):
516
514
  ow = self._original_widget
517
515
  ow_base = self.scrolling_base_widget
518
516
 
519
- if isinstance(ow, SupportsRelativeScroll) and ow.require_relative_scroll(size, focus):
520
- if len(ow) == ow.get_visible_amount(size, focus):
521
- # Canvas fits without scrolling - no scrollbar needed
522
- return render_no_scrollbar()
517
+ # Use hasattr instead of protocol: hasattr will return False in case of getattr raise AttributeError
518
+ # Use __length_hint__ first since it's less resource intensive
519
+ use_relative = (
520
+ isinstance(ow_base, SupportsRelativeScroll)
521
+ and any(hasattr(ow_base, attrib) for attrib in ("__length_hint__", "__len__"))
522
+ and ow_base.require_relative_scroll(size, focus)
523
+ )
523
524
 
525
+ if use_relative:
526
+ # `operator.length_hint` is Protocol (Spec) over class based and can end false-negative on the instance
527
+ # use length_hint-like approach with safe `AttributeError` handling
528
+ ow_len = getattr(ow_base, "__len__", getattr(ow_base, "__length_hint__", lambda: 0))()
524
529
  ow_canv = render_for_scrollbar()
525
- visible_amount = ow.get_visible_amount(ow_size, focus)
530
+ visible_amount = ow_base.get_visible_amount(ow_size, focus)
526
531
  pos = ow_base.get_first_visible_pos(ow_size, focus)
527
- posmax = len(ow) - visible_amount
528
- thumb_weight = min(1.0, visible_amount / max(1, len(ow)))
529
532
 
530
- else:
533
+ # in the case of estimated length, it can be smaller than real widget length
534
+ ow_len = max(ow_len, visible_amount, pos)
535
+ posmax = ow_len - visible_amount
536
+ thumb_weight = min(1.0, visible_amount / max(1, ow_len))
537
+
538
+ if ow_len == visible_amount:
539
+ # Corner case: formally all contents indexes should be visible, but this does not mean all rows
540
+ use_relative = False
541
+
542
+ if not use_relative:
531
543
  ow_rows_max = ow_base.rows_max(size, focus)
532
544
  if ow_rows_max <= maxrow:
533
545
  # Canvas fits without scrolling - no scrollbar needed
urwid/widget/widget.py CHANGED
@@ -592,7 +592,7 @@ class FlowWidget(Widget):
592
592
  )
593
593
  super().__init__()
594
594
 
595
- def rows(self, size: int, focus: bool = False) -> int:
595
+ def rows(self, size: tuple[int], focus: bool = False) -> int:
596
596
  """
597
597
  All flow widgets must implement this function.
598
598
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: urwid
3
- Version: 2.6.9
3
+ Version: 2.6.11
4
4
  Summary: A full-featured console (xterm et al.) user interface library
5
5
  Home-page: https://urwid.org/
6
6
  Author-email: Ian Ward <ian@excess.org>
@@ -11,7 +11,7 @@ urwid/split_repr.py,sha256=pXzuddzQ4RnfIl537Gvoe8PVaBRHCPnxgdYvKK0qm8k,3899
11
11
  urwid/str_util.py,sha256=1ss-LHAhjXTynTVBcoSgYWIpBeN_RirqHYhP7fq7MrA,10844
12
12
  urwid/text_layout.py,sha256=lHiGfo7clmwHt5dMl_AaQSs2ov9IbY9JySTATZBm7h0,22821
13
13
  urwid/util.py,sha256=pGXnma03_IBx3v6_qN9cDWPXPj_YjkhQ9IxLjm_0TpU,15747
14
- urwid/version.py,sha256=muQ56uz1uuUGvBY5I4hh-zAqE-_w_qyq9Go2a181wWs,411
14
+ urwid/version.py,sha256=xlDSe3Xh6QX74LV4NLD5AhuGgpD8erExdJU0ZVYLBLM,413
15
15
  urwid/vterm.py,sha256=q9-8dxJpVr_7a0Bj_z_FYDiRgzos3IS17BjhYcy1RDw,58410
16
16
  urwid/wimp.py,sha256=o1YsjL_tBLXba8ZRr1MTHH0poLSlXT_atY3-uD_Ualg,567
17
17
  urwid/display/__init__.py,sha256=y90WbHPNRHlimPrXhzsSfFsBbBHy8QErmB1y4Xza-xo,1732
@@ -53,22 +53,22 @@ urwid/widget/filler.py,sha256=EEnyAawdKXuwf79pErfNuvqsU1SVTutcMUrwWkU4wfo,14665
53
53
  urwid/widget/frame.py,sha256=AOe4FwjvwcIwrpXqyZkCwSZWwAALNl1XRMAKx6ZXYWs,21834
54
54
  urwid/widget/grid_flow.py,sha256=otujeOTWYDr4gwuDikBm9zZd8SUgma9yvQ1hDWI-dGk,21514
55
55
  urwid/widget/line_box.py,sha256=V750xiZtkw6_uRXLhNY91ER3pXwwrZstVv_IJUZd_YY,6884
56
- urwid/widget/listbox.py,sha256=54DrfJhT9MgSyakz_9ODEx1uAIyR47ax0-PYr4bVC1c,73579
57
- urwid/widget/monitored_list.py,sha256=8lBb8kowLAqKMKByiBZp4jt9PSnXjYAoAjXFXm-7xcg,18773
56
+ urwid/widget/listbox.py,sha256=EaGo13fpt4Oy9V00n27U-2PhOWa7-nmsTgnddKv9GII,74392
57
+ urwid/widget/monitored_list.py,sha256=mZUPYB1yhuKN6su6_VjmoiE0LlTXsYxkH14pbif0tbc,19264
58
58
  urwid/widget/overlay.py,sha256=7EtTHOomIF4xzwYiY7SiMCQZF2IHrmO88YeosFOMFo0,34505
59
- urwid/widget/padding.py,sha256=tdBJybrKJqR8OLo5DHixn6KZA3tefYlfo9kkqvxYLEc,21252
59
+ urwid/widget/padding.py,sha256=pBXQsnkySV1GXgveg2Ivm6SCqHiGa2YkYSjXaoCUIHk,21334
60
60
  urwid/widget/pile.py,sha256=aIzKYNrSQtbHVk_bn2PElHUiSdsO_NKnL4mTu-sXm4I,38628
61
- urwid/widget/popup.py,sha256=UQ1MTzt8RNcfIvmwIqDM4mypxxXUOMsGoiozom8HfTg,5814
61
+ urwid/widget/popup.py,sha256=vw9_S1UC6UMy5Roc4qK8NXgH5_dDGxoHmpDAi_u9wsI,5974
62
62
  urwid/widget/progress_bar.py,sha256=HRnIu2V2_4wgsnAdrhK-GVVToyLaXRH0gtlkWllA4Mo,4767
63
- urwid/widget/scrollable.py,sha256=vQ0dNMo3G0QAXShhU0E4pn7umdIre43nKi-R_Zjeenk,23490
63
+ urwid/widget/scrollable.py,sha256=yB3S3k5dOdZ-wrQMlgxQH-1AZONSf5X6Y2accIa9tpk,24279
64
64
  urwid/widget/solid_fill.py,sha256=NZRDSRK0lP4r7gXcKDgVaKEoeOcWvtrY5k2aueQEEL4,1260
65
65
  urwid/widget/text.py,sha256=jy15hL6rCBoJdZviq2jJ-i9eO6vEcxvzCIVAZs12AUw,12187
66
66
  urwid/widget/treetools.py,sha256=5s3Dnp2PCz4wQccutmdvsTTbAROJMLehXe4moB5POY0,17387
67
- urwid/widget/widget.py,sha256=dMGNVewj2NGFdGKAtDJYgOzhuJAtrr_BALOMwNzUyj0,29550
67
+ urwid/widget/widget.py,sha256=A0ZjvDjLqhdzelPFC96Ozo1voIoGkxftNmJh8X1IyNI,29557
68
68
  urwid/widget/widget_decoration.py,sha256=aHP9Y7JKVCI2dw2OdRrzMj9p--C1pMIbxSJskWBLIgc,5633
69
69
  urwid/widget/wimp.py,sha256=4wfzTQBLMbhicSlL64hBPb-1W5FAFrIKfb43i10MKSY,27305
70
- urwid-2.6.9.dist-info/COPYING,sha256=NrbT-keRaUP9X-wxPFhHhJRgR-wTN6eLRA5ZkstZX4k,26434
71
- urwid-2.6.9.dist-info/METADATA,sha256=nuNLnSVXGabLOUoJz-XzAQV27vwKfYpoqfiHwlfCztc,11042
72
- urwid-2.6.9.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
73
- urwid-2.6.9.dist-info/top_level.txt,sha256=AwxQA43kNkjHbhYELXHBKrQ01X5CR2KnDzU07cVqilY,6
74
- urwid-2.6.9.dist-info/RECORD,,
70
+ urwid-2.6.11.dist-info/COPYING,sha256=NrbT-keRaUP9X-wxPFhHhJRgR-wTN6eLRA5ZkstZX4k,26434
71
+ urwid-2.6.11.dist-info/METADATA,sha256=BdPqGaA0WgGq3ISj7YQYEMzmQKAGoFn6OBAVpJE1Fl0,11043
72
+ urwid-2.6.11.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
73
+ urwid-2.6.11.dist-info/top_level.txt,sha256=AwxQA43kNkjHbhYELXHBKrQ01X5CR2KnDzU07cVqilY,6
74
+ urwid-2.6.11.dist-info/RECORD,,
File without changes