Skip to content

Commit

Permalink
Tweak progress bar docs. (#3286)
Browse files Browse the repository at this point in the history
* Tweak progress bar docs.

There is no good reason as to why the progress bar can't be set back to its indeterminate state (and you could actually do it with code) so this removes the docstring that says that a progress bar can't go back to its indeterminate state.

Related issue: #3268
Related Discord message: https://discord.com/channels/1026214085173461072/1033754296224841768/1149742624002023594

* Use a special sentinal in ProgressBar.update

To comply with #3286 (review) we create a new type around a sentinel object and check whether we're using the sentinel before modifying the progress bar reactives.

Things that didn't quite work well:
- directly checking 'if parameter is not _sentinel:' won't satisfy type checkers because that condition doesn't restrict the type of 'parameter' to _not_ be 'UnsetParameter'.
- checking 'isinstance(parameter, float)' isn't enough because the user may call the method with an integer like '3' and then the isinstance check would fail.

- checking 'isinstance(parameter, (int, float))' works but looks a bit odd, plus it is not very general.

* Rework ProgressBar.update with a sentinel value.
  • Loading branch information
rodrigogiraoserrao authored Sep 20, 2023
1 parent dfba992 commit 79e9f3b
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Breaking change: Widget.notify and App.notify now return None https://github.com/Textualize/textual/pull/3275
- App.unnotify is now private (renamed to App._unnotify) https://github.com/Textualize/textual/pull/3275
- `Markdown.load` will now attempt to scroll to a related heading if an anchor is provided https://github.com/Textualize/textual/pull/3244
- `ProgressBar` explicitly supports being set back to its indeterminate state https://github.com/Textualize/textual/pull/3286

## [0.36.0] - 2023-09-05

Expand Down
4 changes: 4 additions & 0 deletions src/textual/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ def post_message(self, message: "Message") -> bool:
...


class UnusedParameter:
"""Helper type for a parameter that isn't specified in a method call."""


SegmentLines = List[List["Segment"]]
CallbackType = Union[Callable[[], Awaitable[None]], Callable[[], None]]
"""Type used for arbitrary callables used in callbacks."""
Expand Down
2 changes: 2 additions & 0 deletions src/textual/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
CallbackType,
IgnoreReturnCallbackType,
MessageTarget,
UnusedParameter,
WatchCallbackType,
)
from .actions import ActionParseResult
Expand All @@ -29,5 +30,6 @@
"MessageTarget",
"NoActiveAppError",
"RenderStyles",
"UnusedParameter",
"WatchCallbackType",
]
31 changes: 16 additions & 15 deletions src/textual/widgets/_progress_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@

from rich.style import Style

from textual.geometry import clamp

from .._types import UnusedParameter
from ..app import ComposeResult, RenderResult
from ..containers import Horizontal
from ..geometry import clamp
from ..reactive import reactive
from ..renderables.bar import Bar as BarRenderable
from ..timer import Timer
from ..widget import Widget
from ..widgets import Label

UNUSED = UnusedParameter()
"""Sentinel for method signatures."""


class Bar(Widget, can_focus=False):
"""The bar portion of the progress bar."""
Expand Down Expand Up @@ -276,7 +279,6 @@ class ProgressBar(Widget, can_focus=False):
"""The total number of steps associated with this progress bar, when known.
The value `None` will render an indeterminate progress bar.
Once `total` is set to a numerical value, it cannot be set back to `None`.
"""
percentage: reactive[float | None] = reactive[Optional[float]](None)
"""The percentage of progress that has been completed.
Expand Down Expand Up @@ -398,6 +400,7 @@ def advance(self, advance: float = 1) -> None:
```py
progress_bar.advance(10) # Advance 10 steps.
```
Args:
advance: Number of steps to advance progress by.
"""
Expand All @@ -406,30 +409,28 @@ def advance(self, advance: float = 1) -> None:
def update(
self,
*,
total: float | None = None,
progress: float | None = None,
advance: float | None = None,
total: None | float | UnusedParameter = UNUSED,
progress: float | UnusedParameter = UNUSED,
advance: float | UnusedParameter = UNUSED,
) -> None:
"""Update the progress bar with the given options.
Options only affect the progress bar if they are not `None`.
Example:
```py
progress_bar.update(
total=200, # Set new total to 200 steps.
progress=None, # This has no effect.
progress=50, # Set the progress to 50 (out of 200).
)
```
Args:
total: New total number of steps (if not `None`).
progress: Set the progress to the given number of steps (if not `None`).
advance: Advance the progress by this number of steps (if not `None`).
total: New total number of steps.
progress: Set the progress to the given number of steps.
advance: Advance the progress by this number of steps.
"""
if total is not None:
if not isinstance(total, UnusedParameter):
self.total = total
if progress is not None:
if not isinstance(progress, UnusedParameter):
self.progress = progress
if advance is not None:
if not isinstance(advance, UnusedParameter):
self.progress += advance
11 changes: 10 additions & 1 deletion tests/test_progress_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def test_update_total():
assert pb.total == 1000

pb.update(total=None)
assert pb.total == 1000
assert pb.total is None

pb.update(total=100)
assert pb.total == 100
Expand Down Expand Up @@ -119,6 +119,15 @@ def test_update():
assert pb.progress == 50


def test_go_back_to_indeterminate():
pb = ProgressBar()

pb.total = 100
assert pb.percentage == 0
pb.total = None
assert pb.percentage is None


@pytest.mark.parametrize(
["show_bar", "show_percentage", "show_eta"],
[
Expand Down

0 comments on commit 79e9f3b

Please sign in to comment.