urwid 2.6.9__py3-none-any.whl → 2.6.10__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.10'
16
+ __version_tuple__ = version_tuple = (2, 6, 10)
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()
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: urwid
3
- Version: 2.6.9
3
+ Version: 2.6.10
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=Q6B6SJpzx7ArqUrw_2w87t72hs-NzqHnm-ERmJDUGVM,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
56
+ urwid/widget/listbox.py,sha256=EaGo13fpt4Oy9V00n27U-2PhOWa7-nmsTgnddKv9GII,74392
57
57
  urwid/widget/monitored_list.py,sha256=8lBb8kowLAqKMKByiBZp4jt9PSnXjYAoAjXFXm-7xcg,18773
58
58
  urwid/widget/overlay.py,sha256=7EtTHOomIF4xzwYiY7SiMCQZF2IHrmO88YeosFOMFo0,34505
59
59
  urwid/widget/padding.py,sha256=tdBJybrKJqR8OLo5DHixn6KZA3tefYlfo9kkqvxYLEc,21252
60
60
  urwid/widget/pile.py,sha256=aIzKYNrSQtbHVk_bn2PElHUiSdsO_NKnL4mTu-sXm4I,38628
61
61
  urwid/widget/popup.py,sha256=UQ1MTzt8RNcfIvmwIqDM4mypxxXUOMsGoiozom8HfTg,5814
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
67
  urwid/widget/widget.py,sha256=dMGNVewj2NGFdGKAtDJYgOzhuJAtrr_BALOMwNzUyj0,29550
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.10.dist-info/COPYING,sha256=NrbT-keRaUP9X-wxPFhHhJRgR-wTN6eLRA5ZkstZX4k,26434
71
+ urwid-2.6.10.dist-info/METADATA,sha256=o9GKYe-Nt5Hkz7EF_S6E3DXp8CblUs66umXNmAj5-jI,11043
72
+ urwid-2.6.10.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
73
+ urwid-2.6.10.dist-info/top_level.txt,sha256=AwxQA43kNkjHbhYELXHBKrQ01X5CR2KnDzU07cVqilY,6
74
+ urwid-2.6.10.dist-info/RECORD,,
File without changes