Skip to content

Commit 31e2b41

Browse files
authored
Merge pull request #6374 from Textualize/blank-to-null
change sentinal name
2 parents e49cc94 + 725cb8b commit 31e2b41

6 files changed

Lines changed: 44 additions & 44 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1818

1919
- It is no longer a NOOP and warning to dismiss a non-active screen. The dismiss will still work, but the screen may not update if the current mode is not active. https://github.com/Textualize/textual/pull/6362
2020
- Added 50ms delay when switching screens to allow state to udpate and prevent janky flash of old content https://github.com/Textualize/textual/pull/6362
21+
- Breaking change: Changed `Select.BLANK` to `Select.NULL` to avoid clash with newer `Widget.BLANK` Classvar https://github.com/Textualize/textual/pull/6374
2122

2223
## [7.5.0] - 2026-01-30
2324

docs/widgets/select.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ The following example presents a `Select` created using the `from_values` class
8989
## Blank state
9090

9191
The `Select` widget has an option `allow_blank` for its constructor.
92-
If set to `True`, the widget may be in a state where there is no selection, in which case its value will be the special constant [`Select.BLANK`][textual.widgets.Select.BLANK].
92+
If set to `True`, the widget may be in a state where there is no selection, in which case its value will be the special constant [`Select.NULL`][textual.widgets.Select.NULL].
9393
The auxiliary methods [`Select.is_blank`][textual.widgets.Select.is_blank] and [`Select.clear`][textual.widgets.Select.clear] provide a convenient way to check if the widget is in this state and to set this state, respectively.
9494

9595
## Type to search
@@ -98,10 +98,10 @@ The `Select` widget has a `type_to_search` attribute which allows you to type to
9898

9999
## Reactive Attributes
100100

101-
| Name | Type | Default | Description |
102-
|------------|--------------------------------|------------------------------------------------|-------------------------------------|
103-
| `expanded` | `bool` | `False` | True to expand the options overlay. |
104-
| `value` | `SelectType` \| `_NoSelection` | [`Select.BLANK`][textual.widgets.Select.BLANK] | Current value of the Select. |
101+
| Name | Type | Default | Description |
102+
| ---------- | ------------------------------ | -------------------------------------------- | ----------------------------------- |
103+
| `expanded` | `bool` | `False` | True to expand the options overlay. |
104+
| `value` | `SelectType` \| `_NoSelection` | [`Select.NULL`][textual.widgets.Select.NULL] | Current value of the Select. |
105105

106106
## Messages
107107

src/textual/widgets/_select.py

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from typing import TYPE_CHECKING, Generic, Hashable, Iterable, TypeVar, Union
4+
from typing import TYPE_CHECKING, Generic, Hashable, Iterable, TypeVar
55

66
import rich.repr
77
from rich.console import RenderableType
@@ -28,13 +28,13 @@ class NonSelectableStatic(Static):
2828

2929

3030
class NoSelection:
31-
"""Used by the `Select` widget to flag the unselected state. See [`Select.BLANK`][textual.widgets.Select.BLANK]."""
31+
"""Used by the `Select` widget to flag the unselected state. See [`Select.NULL`][textual.widgets.Select.NULL]."""
3232

3333
def __repr__(self) -> str:
34-
return "Select.BLANK"
34+
return "Select.NULL"
3535

3636

37-
BLANK = NoSelection()
37+
NULL = NoSelection()
3838

3939

4040
class InvalidSelectValueError(Exception):
@@ -240,7 +240,7 @@ def __init__(self, placeholder: str) -> None:
240240
"""
241241
super().__init__()
242242
self.placeholder = placeholder
243-
self.label: RenderableType | NoSelection = Select.BLANK
243+
self.label: RenderableType | NoSelection = Select.NULL
244244

245245
def update(self, label: RenderableType | NoSelection) -> None:
246246
"""Update the content in the widget.
@@ -249,7 +249,7 @@ def update(self, label: RenderableType | NoSelection) -> None:
249249
label: A renderable to display, or `None` for the placeholder.
250250
"""
251251
self.label = label
252-
self.has_value = label is not Select.BLANK
252+
self.has_value = label is not Select.NULL
253253
self.query_one("#label", Static).update(
254254
self.placeholder if isinstance(label, NoSelection) else label
255255
)
@@ -283,7 +283,7 @@ class Select(Generic[SelectType], Vertical, can_focus=True):
283283
When activated with ++enter++ the widget displays an overlay with a list of all possible options.
284284
"""
285285

286-
BLANK = BLANK
286+
NULL = NULL
287287
"""Constant to flag that the widget has no selection."""
288288

289289
BINDINGS = [
@@ -356,12 +356,10 @@ class Select(Generic[SelectType], Vertical, can_focus=True):
356356
"""True to show the overlay, otherwise False."""
357357
prompt: var[str] = var[str]("Select")
358358
"""The prompt to show when no value is selected."""
359-
value: var[SelectType | NoSelection] = var[Union[SelectType, NoSelection]](
360-
BLANK, init=False
361-
)
359+
value: var[SelectType | NoSelection] = var(NULL, init=False)
362360
"""The value of the selection.
363361
364-
If the widget has no selection, its value will be [`Select.BLANK`][textual.widgets.Select.BLANK].
362+
If the widget has no selection, its value will be [`Select.NULL`][textual.widgets.Select.NULL].
365363
Setting this to an illegal value will raise a [`InvalidSelectValueError`][textual.widgets.select.InvalidSelectValueError]
366364
exception.
367365
"""
@@ -403,7 +401,7 @@ def __init__(
403401
*,
404402
prompt: str = "Select",
405403
allow_blank: bool = True,
406-
value: SelectType | NoSelection = BLANK,
404+
value: SelectType | NoSelection = NULL,
407405
type_to_search: bool = True,
408406
name: str | None = None,
409407
id: str | None = None,
@@ -420,7 +418,7 @@ def __init__(
420418
prompt: Text to show in the control when no option is selected.
421419
allow_blank: Enables or disables the ability to have the widget in a state
422420
with no selection made, in which case its value is set to the constant
423-
[`Select.BLANK`][textual.widgets.Select.BLANK].
421+
[`Select.NULL`][textual.widgets.Select.NULL].
424422
value: Initial value selected. Should be one of the values in `options`.
425423
If no initial value is set and `allow_blank` is `False`, the widget
426424
will auto-select the first available option.
@@ -452,7 +450,7 @@ def from_values(
452450
*,
453451
prompt: str = "Select",
454452
allow_blank: bool = True,
455-
value: SelectType | NoSelection = BLANK,
453+
value: SelectType | NoSelection = NULL,
456454
type_to_search: bool = True,
457455
name: str | None = None,
458456
id: str | None = None,
@@ -470,7 +468,7 @@ def from_values(
470468
prompt: Text to show in the control when no option is selected.
471469
allow_blank: Enables or disables the ability to have the widget in a state
472470
with no selection made, in which case its value is set to the constant
473-
[`Select.BLANK`][textual.widgets.Select.BLANK].
471+
[`Select.NULL`][textual.widgets.Select.NULL].
474472
value: Initial value selected. Should be one of the values in `values`.
475473
If no initial value is set and `allow_blank` is `False`, the widget
476474
will auto-select the first available value.
@@ -522,7 +520,7 @@ def _setup_variables_for_options(
522520
"""
523521
self._options: list[tuple[RenderableType, SelectType | NoSelection]] = []
524522
if self._allow_blank:
525-
self._options.append(("", self.BLANK))
523+
self._options.append(("", self.NULL))
526524
self._options.extend(options)
527525

528526
if not self._options:
@@ -539,7 +537,7 @@ def _setup_options_renderables(self) -> None:
539537
options: list[Option] = [
540538
(
541539
Option(Text(self.prompt, style="dim"))
542-
if value == self.BLANK
540+
if value == self.NULL
543541
else Option(prompt)
544542
)
545543
for prompt, value in self._options
@@ -549,9 +547,9 @@ def _setup_options_renderables(self) -> None:
549547
option_list.clear_options()
550548
option_list.add_options(options)
551549

552-
def _init_selected_option(self, hint: SelectType | NoSelection = BLANK) -> None:
550+
def _init_selected_option(self, hint: SelectType | NoSelection = NULL) -> None:
553551
"""Initialises the selected option for the `Select`."""
554-
if hint == self.BLANK and not self._allow_blank:
552+
if hint == self.NULL and not self._allow_blank:
555553
hint = self._options[0][1]
556554
self.value = hint
557555

@@ -604,8 +602,8 @@ def _watch_value(self, value: SelectType | NoSelection) -> None:
604602
except NoMatches:
605603
pass
606604
else:
607-
if value == self.BLANK:
608-
select_current.update(self.BLANK)
605+
if value == self.NULL:
606+
select_current.update(self.NULL)
609607
else:
610608
for index, (prompt, _value) in enumerate(self._options):
611609
if _value == value:
@@ -637,7 +635,7 @@ def _watch_expanded(self, expanded: bool) -> None:
637635
self.set_class(expanded, "-expanded")
638636
if expanded:
639637
overlay.focus(scroll_visible=False)
640-
if self.value is self.BLANK:
638+
if self.value is self.NULL:
641639
overlay.select(None)
642640
self.query_one(SelectCurrent).has_value = False
643641
else:
@@ -690,7 +688,7 @@ def is_blank(self) -> bool:
690688
Returns:
691689
True if the selection is blank, False otherwise.
692690
"""
693-
return self.value == self.BLANK
691+
return self.value == self.NULL
694692

695693
def clear(self) -> None:
696694
"""Clear the selection if `allow_blank` is `True`.
@@ -699,7 +697,7 @@ def clear(self) -> None:
699697
InvalidSelectValueError: If `allow_blank` is set to `False`.
700698
"""
701699
try:
702-
self.value = self.BLANK
700+
self.value = self.NULL
703701
except InvalidSelectValueError:
704702
raise InvalidSelectValueError(
705703
"Can't clear selection if allow_blank is set to False."
@@ -712,7 +710,7 @@ def _watch_prompt(self, prompt: str) -> None:
712710
select_current.placeholder = prompt
713711
if not self._allow_blank:
714712
return
715-
if self.value == self.BLANK:
716-
select_current.update(self.BLANK)
713+
if self.value == self.NULL:
714+
select_current.update(self.NULL)
717715
option_list = self.query_one(SelectOverlay)
718716
option_list.replace_option_prompt_at_index(0, Text(prompt, style="dim"))

src/textual/widgets/select.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from textual.widgets._select import (
2-
BLANK,
2+
NULL,
33
EmptySelectError,
44
InvalidSelectValueError,
55
NoSelection,
66
)
77

8-
__all__ = ["EmptySelectError", "InvalidSelectValueError", "NoSelection", "BLANK"]
8+
__all__ = ["EmptySelectError", "InvalidSelectValueError", "NoSelection", "NULL"]

tests/select/test_blank_and_clear.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def compose(self):
1515
app = SelectApp()
1616
async with app.run_test():
1717
select = app.query_one(Select)
18-
assert select.value == Select.BLANK
18+
assert select.value == Select.NULL
1919
assert select.is_blank()
2020

2121

@@ -27,15 +27,15 @@ def compose(self):
2727
app = SelectApp()
2828
async with app.run_test():
2929
select = app.query_one(Select)
30-
assert select.value == Select.BLANK
30+
assert select.value == Select.NULL
3131
assert select.is_blank()
3232

3333
select.value = 0
34-
assert select.value != Select.BLANK
34+
assert select.value != Select.NULL
3535
assert not select.is_blank()
3636

37-
select.value = Select.BLANK
38-
assert select.value == Select.BLANK
37+
select.value = Select.NULL
38+
assert select.value == Select.NULL
3939
assert select.is_blank()
4040

4141

@@ -64,6 +64,7 @@ def compose(self):
6464
with pytest.raises(InvalidSelectValueError):
6565
select.clear()
6666

67+
6768
async def test_selection_is_none_with_blank():
6869
class SelectApp(App[None]):
6970
def compose(self):

tests/select/test_value.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010

1111
class SelectApp(App[None]):
12-
def __init__(self, initial_value=Select.BLANK):
12+
def __init__(self, initial_value=Select.NULL):
1313
self.initial_value = initial_value
1414
super().__init__()
1515

@@ -51,12 +51,12 @@ def compose(self):
5151

5252

5353
async def test_value_assign_to_blank():
54-
"""Setting the value to BLANK should work with default `allow_blank` value."""
54+
"""Setting the value to NULL should work with default `allow_blank` value."""
5555
app = SelectApp(1)
5656
async with app.run_test():
5757
select = app.query_one(Select)
5858
assert select.value == 1
59-
select.value = Select.BLANK
59+
select.value = Select.NULL
6060
assert select.is_blank()
6161

6262

@@ -85,7 +85,7 @@ def compose(self):
8585

8686

8787
async def test_set_value_to_blank_with_allow_blank_false():
88-
"""Setting the value to BLANK with allow_blank=False should raise an error."""
88+
"""Setting the value to NULL with allow_blank=False should raise an error."""
8989

9090
class SelectApp(App[None]):
9191
def compose(self):
@@ -94,11 +94,11 @@ def compose(self):
9494
app = SelectApp()
9595
async with app.run_test():
9696
with pytest.raises(InvalidSelectValueError):
97-
app.query_one(Select).value = Select.BLANK
97+
app.query_one(Select).value = Select.NULL
9898

9999

100100
async def test_set_options_resets_value_to_blank():
101-
"""Resetting the options should reset the value to BLANK."""
101+
"""Resetting the options should reset the value to NULL."""
102102

103103
class SelectApp(App[None]):
104104
def compose(self):

0 commit comments

Comments
 (0)