Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions stubs/WTForms/@tests/stubtest_allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,13 @@ wtforms.meta.DefaultMeta.__getattr__
# Should allow setting and deleting any attribute
wtforms.fields.core.Flags.__delattr__
wtforms.fields.core.Flags.__setattr__

# Error: is inconsistent
# =============================
# __post_init__ ensures label never is None
wtforms.fields.choices.SelectChoice.__init__

# Error: variable differs from runtime type
# =============================
# __post_init__ ensures label never is None
wtforms.fields.choices.SelectChoice.label
145 changes: 100 additions & 45 deletions stubs/WTForms/@tests/test_cases/check_choices.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,122 @@
from __future__ import annotations

from wtforms import SelectField
from enum import Enum, auto

from wtforms import Field, Form, SelectChoice, SelectField, StringField
from wtforms.fields import enum_choices, enum_coerce

# construct SelectChoice from old inputs
SelectChoice.from_input("")
SelectChoice.from_input(("", ""))
SelectChoice.from_input(("", "", {}))
# non-str values are no longer allowed/encouraged
# technically they never were, it just never was enforced
# coerce(value) is supposed to yield the object that gets stored in data
# so values in choices are intended to be pre-serialized to str in the
# format coerce expects
SelectChoice.from_input((1, "1")) # type: ignore[arg-type]
SelectChoice.from_input((True, "t", {})) # type: ignore[arg-type]
SelectChoice.from_input((False, "f", {})) # type: ignore[arg-type]

# provide choices as any iterable of SelectChoice
SelectField(choices=[SelectChoice("")])

# new style of grouped choices
SelectField(choices=[SelectChoice("A", optgroup="a"), SelectChoice("B", optgroup="b")])

# provide a callback without arguments that generates a sequence of SelectChoice
SelectField(choices=lambda: [SelectChoice("")])


# or a callback that receives the form as its first and the field as its second argument
def generate_choices(form: Form, field: SelectField) -> list[SelectChoice]:
return []


def generate_choices_generous_field_type(form: Form, field: Field) -> list[SelectChoice]:
return []


def generate_choices_wrong_field_type(form: Form, field: StringField) -> list[SelectChoice]:
return []


SelectField(choices=lambda form, field: [SelectChoice("")])
SelectField(choices=generate_choices)
SelectField(choices=generate_choices_generous_field_type)
SelectField(choices=generate_choices_wrong_field_type) # type: ignore


# provide choices based on enum
class TestEnum(Enum):
foo = auto()
bar = auto()
baz = auto()


SelectField(choices=enum_choices(TestEnum), coerce=enum_coerce(TestEnum))

# deprecated ways of providing choices:
# any way we can specify the choices inline with a literal should work

# tuple of tuples
SelectField(choices=(("", ""),))
SelectField(choices=((1, "1"),))
SelectField(choices=(("", "", {}),))
SelectField(choices=((True, "t", {}),))
SelectField(choices=((True, "t"), (False, "f", {})))
SelectField(choices=(("", ""),)) # pyright: ignore[reportDeprecated]
SelectField(choices=((1, "1"),)) # pyright: ignore[reportDeprecated]
SelectField(choices=(("", "", {}),)) # pyright: ignore[reportDeprecated]
SelectField(choices=((True, "t", {}),)) # pyright: ignore[reportDeprecated]
SelectField(choices=((True, "t"), (False, "f", {}))) # pyright: ignore[reportDeprecated]

# list of tuples
SelectField(choices=[("", "")])
SelectField(choices=[(1, "1")])
SelectField(choices=[("", "", {})])
SelectField(choices=[(True, "t", {})])
SelectField(choices=[(True, "t"), (False, "f", {})])
SelectField(choices=[("", "")]) # pyright: ignore[reportDeprecated]
SelectField(choices=[(1, "1")]) # pyright: ignore[reportDeprecated]
SelectField(choices=[("", "", {})]) # pyright: ignore[reportDeprecated]
SelectField(choices=[(True, "t", {})]) # pyright: ignore[reportDeprecated]
SelectField(choices=[(True, "t"), (False, "f", {})]) # pyright: ignore[reportDeprecated]

# dict of tuple of tuples
SelectField(choices={"a": (("", ""),)})
SelectField(choices={"a": ((1, "1"),)})
SelectField(choices={"a": (("", "", {}),)})
SelectField(choices={"a": ((True, "t", {}),)})
SelectField(choices={"a": ((True, "t"), (False, "f", {}))})
SelectField(choices={"a": ((True, "", {}),), "b": ((False, "f"),)})
SelectField(choices={"a": (("", ""),)}) # pyright: ignore[reportDeprecated]
SelectField(choices={"a": ((1, "1"),)}) # pyright: ignore[reportDeprecated]
SelectField(choices={"a": (("", "", {}),)}) # pyright: ignore[reportDeprecated]
SelectField(choices={"a": ((True, "t", {}),)}) # pyright: ignore[reportDeprecated]
SelectField(choices={"a": ((True, "t"), (False, "f", {}))}) # pyright: ignore[reportDeprecated]
SelectField(choices={"a": ((True, "", {}),), "b": ((False, "f"),)}) # pyright: ignore[reportDeprecated]

# dict of list of tuples
SelectField(choices={"a": [("", "")]})
SelectField(choices={"a": [(1, "1")]})
SelectField(choices={"a": [("", "", {})]})
SelectField(choices={"a": [(True, "t", {})]})
SelectField(choices={"a": [(True, "t"), (False, "f", {})]})
SelectField(choices={"a": [(True, "", {})], "b": [(False, "f")]})
SelectField(choices={"a": [("", "")]}) # pyright: ignore[reportDeprecated]
SelectField(choices={"a": [(1, "1")]}) # pyright: ignore[reportDeprecated]
SelectField(choices={"a": [("", "", {})]}) # pyright: ignore[reportDeprecated]
SelectField(choices={"a": [(True, "t", {})]}) # pyright: ignore[reportDeprecated]
SelectField(choices={"a": [(True, "t"), (False, "f", {})]}) # pyright: ignore[reportDeprecated]
SelectField(choices={"a": [(True, "", {})], "b": [(False, "f")]}) # pyright: ignore[reportDeprecated]

# the same should be true for lambdas

# tuple of tuples
SelectField(choices=lambda: (("", ""),))
SelectField(choices=lambda: ((1, "1"),))
SelectField(choices=lambda: (("", "", {}),))
SelectField(choices=lambda: ((True, "t", {}),))
SelectField(choices=lambda: ((True, "t"), (False, "f", {})))
SelectField(choices=lambda: (("", ""),)) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: ((1, "1"),)) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: (("", "", {}),)) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: ((True, "t", {}),)) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: ((True, "t"), (False, "f", {}))) # pyright: ignore[reportDeprecated]

# list of tuples
SelectField(choices=lambda: [("", "")])
SelectField(choices=lambda: [(1, "1")])
SelectField(choices=lambda: [("", "", {})])
SelectField(choices=lambda: [(True, "t", {})])
SelectField(choices=lambda: [(True, "t"), (False, "f", {})])
SelectField(choices=lambda: [("", "")]) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: [(1, "1")]) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: [("", "", {})]) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: [(True, "t", {})]) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: [(True, "t"), (False, "f", {})]) # pyright: ignore[reportDeprecated]

# dict of tuple of tuples
SelectField(choices=lambda: {"a": (("", ""),)})
SelectField(choices=lambda: {"a": ((1, "1"),)})
SelectField(choices=lambda: {"a": (("", "", {}),)})
SelectField(choices=lambda: {"a": ((True, "t", {}),)})
SelectField(choices=lambda: {"a": ((True, "t"), (False, "f", {}))})
SelectField(choices=lambda: {"a": ((True, "", {}),), "b": ((False, "f"),)})
SelectField(choices=lambda: {"a": (("", ""),)}) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: {"a": ((1, "1"),)}) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: {"a": (("", "", {}),)}) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: {"a": ((True, "t", {}),)}) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: {"a": ((True, "t"), (False, "f", {}))}) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: {"a": ((True, "", {}),), "b": ((False, "f"),)}) # pyright: ignore[reportDeprecated]

# dict of list of tuples
SelectField(choices=lambda: {"a": [("", "")]})
SelectField(choices=lambda: {"a": [(1, "1")]})
SelectField(choices=lambda: {"a": [("", "", {})]})
SelectField(choices=lambda: {"a": [(True, "t", {})]})
SelectField(choices=lambda: {"a": [(True, "t"), (False, "f", {})]})
SelectField(choices=lambda: {"a": [(True, "", {})], "b": [(False, "f")]})
SelectField(choices=lambda: {"a": [("", "")]}) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: {"a": [(1, "1")]}) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: {"a": [("", "", {})]}) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: {"a": [(True, "t", {})]}) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: {"a": [(True, "t"), (False, "f", {})]}) # pyright: ignore[reportDeprecated]
SelectField(choices=lambda: {"a": [(True, "", {})], "b": [(False, "f")]}) # pyright: ignore[reportDeprecated]
3 changes: 2 additions & 1 deletion stubs/WTForms/METADATA.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
version = "~= 3.2.1"
version = "3.3.*"
upstream-repository = "https://github.com/pallets-eco/wtforms"
dependencies = ["MarkupSafe"]
requires-python = ">=3.11"
12 changes: 11 additions & 1 deletion stubs/WTForms/wtforms/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from typing import Final

from wtforms import validators as validators, widgets as widgets
from wtforms.datalist import DataList as DataList, DataListChoice as DataListChoice, enum_datalist as enum_datalist
from wtforms.fields.choices import (
Choice as Choice,
RadioField as RadioField,
SelectChoice as SelectChoice,
SelectField as SelectField,
SelectFieldBase as SelectFieldBase,
SelectMultipleField as SelectMultipleField,
Expand All @@ -27,6 +30,7 @@ from wtforms.fields.numeric import (
)
from wtforms.fields.simple import (
BooleanField as BooleanField,
ButtonField as ButtonField,
ColorField as ColorField,
EmailField as EmailField,
FileField as FileField,
Expand All @@ -50,8 +54,8 @@ __all__ = [
"Form",
"ValidationError",
"SelectField",
"SelectFieldBase",
"SelectMultipleField",
"SelectFieldBase",
"RadioField",
"Field",
"Flags",
Expand All @@ -69,7 +73,9 @@ __all__ = [
"FloatField",
"IntegerRangeField",
"DecimalRangeField",
"ButtonField",
"BooleanField",
"DataList",
"TextAreaField",
"PasswordField",
"FileField",
Expand All @@ -82,4 +88,8 @@ __all__ = [
"URLField",
"EmailField",
"ColorField",
"Choice",
"SelectChoice",
"DataListChoice",
"enum_datalist",
]
5 changes: 5 additions & 0 deletions stubs/WTForms/wtforms/_compat.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from collections.abc import Callable
from inspect import Signature
from typing import Any

def get_signature(callable: Callable[..., Any]) -> Signature: ...
43 changes: 43 additions & 0 deletions stubs/WTForms/wtforms/datalist.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from collections.abc import Callable, Iterator, Sequence
from dataclasses import dataclass
from enum import EnumMeta
from typing import Any, Protocol, TypeAlias, type_check_only
from typing_extensions import Self

from markupsafe import Markup
from wtforms.fields import Field

_RawDataListChoice: TypeAlias = str | tuple[str] | tuple[str, str | None] | tuple[str, str | None, dict[str, Any]]
_DataListChoices: TypeAlias = Sequence[DataListChoice | _RawDataListChoice] | dict[str, str]

@type_check_only
class _DataListWidget(Protocol):
def __call__(self, datalist: DataList, /, *, field: Field | None = None, **kwargs: Any) -> Markup: ...

__all__ = ("DataList", "DataListChoice", "enum_datalist")

@dataclass
class DataListChoice:
value: str
label: str | None = ...
render_kw: dict[str, Any] = ...
def __post_init__(self) -> None: ...
def __iter__(self) -> Iterator[tuple[str, str, dict[str, Any]]]: ...
@classmethod
def from_input(cls, input: DataListChoice | _RawDataListChoice) -> Self: ...

def enum_datalist(enum_cls: EnumMeta, *, by: str = "value", label: str | None = None) -> DataList: ...

class DataList:
widget: _DataListWidget
render_kw: dict[str, Any]
id: str | None
def __init__(
self,
choices: _DataListChoices | Callable[[Any, Any], _DataListChoices] | Callable[[], _DataListChoices] | None = None,
*,
render_kw: dict[str, Any] | None = None,
widget: _DataListWidget | None = None,
) -> None: ...
def iter_choices(self, field: Field | None = None) -> list[DataListChoice]: ...
def __call__(self, field: Field | None = None, **kwargs: Any) -> Markup: ...
12 changes: 11 additions & 1 deletion stubs/WTForms/wtforms/fields/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from wtforms.fields.choices import (
Choice as Choice,
RadioField as RadioField,
SelectChoice as SelectChoice,
SelectField as SelectField,
SelectFieldBase as SelectFieldBase,
SelectMultipleField as SelectMultipleField,
enum_choices as enum_choices,
enum_coerce as enum_coerce,
)
from wtforms.fields.core import Field as Field, Flags as Flags, Label as Label
from wtforms.fields.datetime import (
Expand All @@ -24,6 +28,7 @@ from wtforms.fields.numeric import (
)
from wtforms.fields.simple import (
BooleanField as BooleanField,
ButtonField as ButtonField,
ColorField as ColorField,
EmailField as EmailField,
FileField as FileField,
Expand All @@ -43,10 +48,14 @@ __all__ = [
"Field",
"Flags",
"Label",
"Choice",
"SelectChoice",
"SelectField",
"SelectFieldBase",
"SelectMultipleField",
"SelectFieldBase",
"RadioField",
"enum_choices",
"enum_coerce",
"DateTimeField",
"DateField",
"TimeField",
Expand All @@ -59,6 +68,7 @@ __all__ = [
"FloatField",
"IntegerRangeField",
"DecimalRangeField",
"ButtonField",
"BooleanField",
"TextAreaField",
"PasswordField",
Expand Down
Loading
Loading