Add stubs for WTForms (#10557)

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
David Salvisberg
2023-09-29 07:30:29 +02:00
committed by GitHub
parent cb4425f9fa
commit f8a673f12a
24 changed files with 1305 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
# Error: is not present at runtime
# =============================
# This is hack to get around Field.__new__ not being able to return
# UnboundField
wtforms.Field.__get__
wtforms.fields.Field.__get__
wtforms.fields.core.Field.__get__
# Since DefaultMeta can contain arbitrary values we added __getattr__
# to let mypy know that arbitrary attribute access is possible
wtforms.meta.DefaultMeta.__getattr__
# Error: variable differs from runtime
# ======================
# _unbound_fields has some weird semantics: due to the metaclass it
# will be None until the form class has been instantiated at least
# once and then will stick around until someone adds a new field
# to the class, which clears it back to None. Which means on instances
# it will always be there and on the class it depends, so maybe this
# should use a dummy descriptor? For now we just pretend it's set.
# The behavior is documented in FormMeta, so I think it's fine.
wtforms.Form._unbound_fields
wtforms.form.Form._unbound_fields
# widget is both used as a ClassVar and instance variable and does
# not necessarily reflect an upper bound on Widget, so we always use
# our Widget Protocol definition that's contravariant on Self
wtforms.Field.widget
wtforms.FormField.widget
wtforms.SelectField.widget
wtforms.SelectMultipleField.widget
wtforms.TextAreaField.widget
wtforms.fields.Field.widget
wtforms.fields.FormField.widget
wtforms.fields.SelectField.widget
wtforms.fields.SelectMultipleField.widget
wtforms.fields.TextAreaField.widget
wtforms.fields.choices.SelectField.widget
wtforms.fields.choices.SelectMultipleField.widget
wtforms.fields.core.Field.widget
wtforms.fields.form.FormField.widget
wtforms.fields.simple.TextAreaField.widget

View File

@@ -0,0 +1,49 @@
from __future__ import annotations
from wtforms import Field, Form
class Filter1:
def __call__(self, value: object) -> None:
...
class Filter2:
def __call__(self, input: None) -> None:
...
def not_a_filter(a: object, b: object) -> None:
...
def also_not_a_filter() -> None:
...
# we should accept any mapping of sequences, we can't really validate
# the filter functions when it's this nested
form = Form()
form.process(extra_filters={"foo": (str.upper, str.strip, int), "bar": (Filter1(), Filter2())})
form.process(extra_filters={"foo": [str.upper, str.strip, int], "bar": [Filter1(), Filter2()]})
# regardless of how we pass the filters into Field it should work
field = Field(filters=(str.upper, str.lower, int))
Field(filters=(Filter1(), Filter2()))
Field(filters=[str.upper, str.lower, int])
Field(filters=[Filter1(), Filter2()])
field.process(None, extra_filters=(str.upper, str.lower, int))
field.process(None, extra_filters=(Filter1(), Filter2()))
field.process(None, extra_filters=[str.upper, str.lower, int])
field.process(None, extra_filters=[Filter1(), Filter2()])
# but if we pass in some callables with an incompatible param spec
# then we should get type errors
Field(filters=(str.upper, str.lower, int, not_a_filter)) # type:ignore
Field(filters=(Filter1(), Filter2(), also_not_a_filter)) # type:ignore
Field(filters=[str.upper, str.lower, int, also_not_a_filter]) # type:ignore
Field(filters=[Filter1(), Filter2(), not_a_filter]) # type:ignore
field.process(None, extra_filters=(str.upper, str.lower, int, not_a_filter)) # type:ignore
field.process(None, extra_filters=(Filter1(), Filter2(), also_not_a_filter)) # type:ignore
field.process(None, extra_filters=[str.upper, str.lower, int, also_not_a_filter]) # type:ignore
field.process(None, extra_filters=[Filter1(), Filter2(), not_a_filter]) # type:ignore

View File

@@ -0,0 +1,33 @@
from __future__ import annotations
from wtforms import DateField, Field, Form, StringField
from wtforms.validators import Email, Optional
form = Form()
# on form we should accept any validator mapping
form.validate({"field": (Optional(),), "string_field": (Optional(), Email())})
form.validate({"field": [Optional()], "string_field": [Optional(), Email()]})
# both StringField validators and Field validators should be valid
# as inputs on a StringField
string_field = StringField(validators=(Optional(), Email()))
string_field.validate(form, (Optional(), Email()))
# but not on Field
field = Field(validators=(Optional(), Email())) # type:ignore
field.validate(form, (Optional(), Email())) # type:ignore
# unless we only pass the Field validator
Field(validators=(Optional(),))
field.validate(form, (Optional(),))
# DateField should accept Field validators but not StringField validators
date_field = DateField(validators=(Optional(), Email())) # type:ignore
date_field.validate(form, (Optional(), Email())) # type:ignore
DateField(validators=(Optional(),))
# for lists we can't be as strict so we won't get type errors here
Field(validators=[Optional(), Email()])
field.validate(form, [Optional(), Email()])
DateField(validators=[Optional(), Email()])
date_field.validate(form, [Optional(), Email()])

View File

@@ -0,0 +1,26 @@
from __future__ import annotations
from wtforms import Field, FieldList, Form, FormField, SelectField, StringField
from wtforms.widgets import Input, ListWidget, Option, Select, TableWidget, TextArea
# more specific widgets should only work on more specific fields
Field(widget=Input())
Field(widget=TextArea()) # type:ignore
Field(widget=Select()) # type:ignore
# less specific widgets are fine, even if they're often not what you want
StringField(widget=Input())
StringField(widget=TextArea())
SelectField(widget=Input(), option_widget=Input())
SelectField(widget=Select(), option_widget=Option())
# a more specific type other than Option widget is not allowed
SelectField(widget=Select(), option_widget=TextArea()) # type:ignore
# we should be able to pass Field() even though it wants an unbound_field
# this gets around __new__ not working in type checking
FieldList(Field(), widget=Input())
FieldList(Field(), widget=ListWidget())
FormField(Form, widget=Input())
FormField(Form, widget=TableWidget())

View File

@@ -0,0 +1,3 @@
version = "3.0.*"
upstream_repository = "https://github.com/wtforms/wtforms"
requires = ["MarkupSafe"]

View File

@@ -0,0 +1,4 @@
from wtforms import validators as validators, widgets as widgets
from wtforms.fields import *
from wtforms.form import Form as Form
from wtforms.validators import ValidationError as ValidationError

View File

View File

@@ -0,0 +1,39 @@
from abc import abstractmethod
from collections.abc import Callable, Sequence
from typing import Any
from typing_extensions import Self
from wtforms.fields import HiddenField
from wtforms.fields.core import UnboundField, _Filter, _FormT, _Validator, _Widget
from wtforms.form import BaseForm
from wtforms.meta import DefaultMeta, _SupportsGettextAndNgettext
class CSRFTokenField(HiddenField):
current_token: str | None
csrf_impl: CSRF
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: str | Callable[[], str] | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
*,
csrf_impl: CSRF,
) -> None: ...
class CSRF:
field_class: type[CSRFTokenField]
def setup_form(self, form: BaseForm) -> list[tuple[str, UnboundField[Any]]]: ...
@abstractmethod
def generate_csrf_token(self, csrf_token_field: CSRFTokenField) -> str: ...
@abstractmethod
def validate_csrf_token(self, form: BaseForm, field: CSRFTokenField) -> None: ...

View File

@@ -0,0 +1,18 @@
from _typeshed import SupportsItemAccess
from datetime import datetime, timedelta
from typing import Any
from wtforms.csrf.core import CSRF, CSRFTokenField
from wtforms.form import BaseForm
from wtforms.meta import DefaultMeta
class SessionCSRF(CSRF):
TIME_FORMAT: str
form_meta: DefaultMeta
def generate_csrf_token(self, csrf_token_field: CSRFTokenField) -> str: ...
def validate_csrf_token(self, form: BaseForm, field: CSRFTokenField) -> None: ...
def now(self) -> datetime: ...
@property
def time_limit(self) -> timedelta: ...
@property
def session(self) -> SupportsItemAccess[str, Any]: ...

View File

@@ -0,0 +1,8 @@
from wtforms.fields.choices import *
from wtforms.fields.choices import SelectFieldBase as SelectFieldBase
from wtforms.fields.core import Field as Field, Flags as Flags, Label as Label
from wtforms.fields.datetime import *
from wtforms.fields.form import *
from wtforms.fields.list import *
from wtforms.fields.numeric import *
from wtforms.fields.simple import *

View File

@@ -0,0 +1,76 @@
from collections.abc import Callable, Iterable, Iterator, Sequence
from typing import Any
from typing_extensions import Self, TypeAlias
from wtforms.fields.core import Field, _Filter, _FormT, _Validator, _Widget
from wtforms.form import BaseForm
from wtforms.meta import DefaultMeta, _SupportsGettextAndNgettext
# technically this allows a list, but we're more strict for type safety
_Choice: TypeAlias = tuple[Any, str]
_GroupedChoices: TypeAlias = dict[str, Iterable[_Choice]]
_FullChoice: TypeAlias = tuple[Any, str, bool] # value, label, selected
_FullGroupedChoices: TypeAlias = tuple[str, Iterable[_FullChoice]]
_Option: TypeAlias = SelectFieldBase._Option
class SelectFieldBase(Field):
option_widget: _Widget[_Option]
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
option_widget: _Widget[_Option] | None = None,
*,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: object | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
def iter_choices(self) -> Iterator[_FullChoice]: ...
def has_groups(self) -> bool: ...
def iter_groups(self) -> Iterator[_FullGroupedChoices]: ...
def __iter__(self) -> Iterator[_Option]: ...
class _Option(Field):
checked: bool
class SelectField(SelectFieldBase):
coerce: Callable[[Any], Any]
choices: list[_Choice] | _GroupedChoices
validate_choice: bool
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
coerce: Callable[[Any], Any] = ...,
choices: Iterable[_Choice] | _GroupedChoices | None = None,
validate_choice: bool = True,
*,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: object | None = None,
widget: _Widget[Self] | None = None,
option_widget: _Widget[_Option] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
def iter_choices(self) -> Iterator[_FullChoice]: ...
def has_groups(self) -> bool: ...
def iter_groups(self) -> Iterator[_FullGroupedChoices]: ...
class SelectMultipleField(SelectField):
data: list[Any] | None
class RadioField(SelectField): ...

View File

@@ -0,0 +1,132 @@
from builtins import type as _type # type is being shadowed in Field
from collections.abc import Callable, Iterable, Sequence
from typing import Any, Generic, Protocol, TypeVar, overload
from typing_extensions import Self, TypeAlias
from markupsafe import Markup
from wtforms.form import BaseForm
from wtforms.meta import DefaultMeta, _MultiDictLikeWithGetlist, _SupportsGettextAndNgettext
_FormT = TypeVar("_FormT", bound=BaseForm)
_FieldT = TypeVar("_FieldT", bound=Field)
_FormT_contra = TypeVar("_FormT_contra", bound=BaseForm, contravariant=True)
_FieldT_contra = TypeVar("_FieldT_contra", bound=Field, contravariant=True)
# It would be nice to annotate this as invariant, i.e. input type and output type
# needs to be the same, but it will probably be too annoying to use, for now we
# trust, that people won't use it to change the type of data in a field...
_Filter: TypeAlias = Callable[[Any], Any]
class _Validator(Protocol[_FormT_contra, _FieldT_contra]):
def __call__(self, __form: _FormT_contra, __field: _FieldT_contra) -> object: ...
class _Widget(Protocol[_FieldT_contra]):
def __call__(self, field: _FieldT_contra, **kwargs: Any) -> Markup: ...
class Field:
errors: Sequence[str]
process_errors: Sequence[str]
raw_data: list[Any] | None
object_data: Any
data: Any
validators: Sequence[_Validator[Any, Self]]
# even though this could be None on the base class, this should
# never actually be None in a real field
widget: _Widget[Self]
do_not_call_in_templates: bool
meta: DefaultMeta
default: Any | None
description: str
render_kw: dict[str, Any]
filters: Sequence[_Filter]
flags: Flags
name: str
short_name: str
id: str
type: str
label: Label
# technically this can return UnboundField, but that is not allowed
# by type checkers, so we use a descriptor hack to get around this
# limitation instead
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...
def __init__(
self,
label: str | None = None,
# for tuple we can be a bit more type safe and only accept validators
# that would work on this or a less specific field, but in general it
# would be too annoying to restrict to Sequence[_Validator], since mypy
# will infer a list of mixed validators as list[object], since that is
# the common base class between all validators
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: object | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
def __html__(self) -> str: ...
def __call__(self, **kwargs: object) -> Markup: ...
@classmethod
def check_validators(cls, validators: Iterable[_Validator[_FormT, Self]] | None) -> None: ...
def gettext(self, string: str) -> str: ...
def ngettext(self, singular: str, plural: str, n: int) -> str: ...
def validate(self, form: BaseForm, extra_validators: tuple[_Validator[_FormT, Self], ...] | list[Any] = ()) -> bool: ...
def pre_validate(self, form: BaseForm) -> None: ...
def post_validate(self, form: BaseForm, validation_stopped: bool) -> None: ...
def process(
self, formdata: _MultiDictLikeWithGetlist | None, data: Any = ..., extra_filters: Sequence[_Filter] | None = None
) -> None: ...
def process_data(self, value: Any) -> None: ...
def process_formdata(self, valuelist: list[Any]) -> None: ...
def populate_obj(self, obj: object, name: str) -> None: ...
# this is a workaround for what is essentialy illegal in static type checking
# Field.__new__ would return an UnboundField, unless the _form parameter is
# specified. We can't really work around it by making UnboundField a subclass
# of Field, since all subclasses of Field still need to return an UnboundField
# and we can't expect third parties to add a __new__ method to every field
# they define...
# This workaround only works for Form, not BaseForm, but we take what we can get
# BaseForm shouldn't really be used anyways
@overload
def __get__(self, obj: None, owner: _type[object] | None = None) -> UnboundField[Self]: ...
@overload
def __get__(self, obj: object, owner: _type[object] | None = None) -> Self: ...
class UnboundField(Generic[_FieldT]):
creation_counter: int
field_class: type[_FieldT]
name: str | None
args: tuple[Any, ...]
kwargs: dict[str, Any]
def __init__(self, field_class: type[_FieldT], *args: object, name: str | None = None, **kwargs: object) -> None: ...
def bind(
self,
form: BaseForm,
name: str,
prefix: str = "",
translations: _SupportsGettextAndNgettext | None = None,
**kwargs: object,
) -> _FieldT: ...
class Flags:
# the API for this is a bit loosey goosey, the intention probably
# was that the values should always be boolean, but __contains__
# just returns the same thing as __getattr__ and in the widgets
# there are fields that could accept numeric values from Flags
def __getattr__(self, name: str) -> Any | None: ...
def __setattr__(self, name: str, value: object) -> None: ...
def __delattr__(self, name: str) -> None: ...
def __contains__(self, name: str) -> Any | None: ...
class Label:
field_id: str
text: str
def __init__(self, field_id: str, text: str) -> None: ...
def __html__(self) -> str: ...
def __call__(self, text: str | None = None, **kwargs: Any) -> Markup: ...

View File

@@ -0,0 +1,116 @@
from collections.abc import Callable, Sequence
from datetime import date, datetime, time
from typing import Any
from typing_extensions import Self
from wtforms.fields.core import Field, _Filter, _FormT, _Validator, _Widget
from wtforms.form import BaseForm
from wtforms.meta import DefaultMeta, _SupportsGettextAndNgettext
class DateTimeField(Field):
format: list[str]
strptime_format: list[str]
data: datetime | None
default: datetime | Callable[[], datetime] | None
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
format: str | list[str] = "%Y-%m-%d %H:%M:%S",
*,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: datetime | Callable[[], datetime] | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
class DateField(DateTimeField):
data: date | None # type: ignore[assignment]
default: date | Callable[[], date] | None # type: ignore[assignment]
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
format: str | list[str] = "%Y-%m-%d",
*,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: date | Callable[[], date] | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
class TimeField(DateTimeField):
data: time | None # type: ignore[assignment]
default: time | Callable[[], time] | None # type: ignore[assignment]
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
format: str | list[str] = "%H:%M",
*,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: time | Callable[[], time] | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
class MonthField(DateField):
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
format: str | list[str] = "%Y-%m",
*,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: time | Callable[[], time] | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
class DateTimeLocalField(DateTimeField):
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
format: str | list[str] = ...,
*,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: time | Callable[[], time] | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...

View File

@@ -0,0 +1,38 @@
from collections.abc import Iterator, Sequence
from typing import Any, Generic, TypeVar
from wtforms.fields.core import Field, _Widget
from wtforms.form import BaseForm
from wtforms.meta import DefaultMeta, _SupportsGettextAndNgettext
_BoundFormT = TypeVar("_BoundFormT", bound=BaseForm)
class FormField(Field, Generic[_BoundFormT]):
form_class: type[_BoundFormT]
form: _BoundFormT
separator: str
def __init__(
self: FormField[_BoundFormT],
form_class: type[_BoundFormT],
label: str | None = None,
validators: None = None,
separator: str = "-",
*,
description: str = "",
id: str | None = None,
default: object | None = None,
widget: _Widget[FormField[_BoundFormT]] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
def __iter__(self) -> Iterator[Field]: ...
def __getitem__(self, name: str) -> Field: ...
def __getattr__(self, name: str) -> Field: ...
@property
def data(self) -> dict[str, Any]: ...
@property
def errors(self) -> dict[str | None, Sequence[str]]: ... # type: ignore[override]

View File

@@ -0,0 +1,44 @@
from collections.abc import Callable, Iterable, Iterator
from typing import Any, Generic, TypeVar
from wtforms.fields.core import Field, UnboundField, _FormT, _Validator, _Widget
from wtforms.form import BaseForm
from wtforms.meta import DefaultMeta, _SupportsGettextAndNgettext
_BoundFieldT = TypeVar("_BoundFieldT", bound=Field)
class FieldList(Field, Generic[_BoundFieldT]):
unbound_field: UnboundField[_BoundFieldT]
min_entries: int
max_entries: int | None
last_index: int
entries: list[_BoundFieldT]
object_data: Iterable[Any]
def __init__(
self: FieldList[_BoundFieldT],
# because of our workaround we need to accept Field as well
unbound_field: UnboundField[_BoundFieldT] | _BoundFieldT,
label: str | None = None,
validators: tuple[_Validator[_FormT, _BoundFieldT], ...] | list[Any] | None = None,
min_entries: int = 0,
max_entries: int | None = None,
separator: str = "-",
default: Iterable[Any] | Callable[[], Iterable[Any]] = (),
*,
description: str = "",
id: str | None = None,
widget: _Widget[FieldList[Any]] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
def append_entry(self, data: Any = ...) -> _BoundFieldT: ...
def pop_entry(self) -> _BoundFieldT: ...
def __iter__(self) -> Iterator[_BoundFieldT]: ...
def __len__(self) -> int: ...
def __getitem__(self, index: int) -> _BoundFieldT: ...
@property
def data(self) -> list[Any]: ...

View File

@@ -0,0 +1,145 @@
from collections.abc import Callable, Sequence
from decimal import Decimal
from typing import Any, overload
from typing_extensions import Literal, Self
from wtforms.fields.core import Field, _Filter, _FormT, _Validator, _Widget
from wtforms.form import BaseForm
from wtforms.meta import DefaultMeta, _SupportsGettextAndNgettext
from wtforms.utils import UnsetValue
__all__ = ("IntegerField", "DecimalField", "FloatField", "IntegerRangeField", "DecimalRangeField")
class LocaleAwareNumberField(Field):
use_locale: bool
number_format: Any | None
locale: str
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
use_locale: bool = False,
# this accepts a babel.numbers.NumberPattern, but since it
# is an optional dependency we don't want to depend on it
# for annotating this one argument
number_format: str | Any | None = None,
*,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: object | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
class IntegerField(Field):
data: int | None
# technically this is not as strict and will accept anything
# that can be passed into int(), but we might as well be
default: int | Callable[[], int] | None
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
*,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: int | Callable[[], int] | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
class DecimalField(LocaleAwareNumberField):
data: Decimal | None
# technically this is not as strict and will accept anything
# that can be passed into Decimal(), but we might as well be
default: Decimal | Callable[[], Decimal] | None
places: int | None
rounding: str | None
@overload
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
*,
places: UnsetValue = ...,
rounding: None = None,
use_locale: Literal[True],
# this accepts a babel.numbers.NumberPattern, but since it
# is an optional dependency we don't want to depend on it
# for annotation this one argument
number_format: str | Any | None = None,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: Decimal | Callable[[], Decimal] | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
@overload
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
places: int | UnsetValue | None = ...,
rounding: str | None = None,
*,
use_locale: Literal[False] = False,
# this accepts a babel.numbers.NumberPattern, but since it
# is an optional dependency we don't want to depend on it
# for annotation this one argument
number_format: str | Any | None = None,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: Decimal | Callable[[], Decimal] | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
class FloatField(Field):
data: float | None
# technically this is not as strict and will accept anything
# that can be passed into float(), but we might as well be
default: float | Callable[[], float] | None
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
*,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: float | Callable[[], float] | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
class IntegerRangeField(IntegerField): ...
class DecimalRangeField(DecimalField): ...

View File

@@ -0,0 +1,64 @@
from collections.abc import Callable, Collection, Sequence
from typing import Any
from typing_extensions import Self
from wtforms.fields.core import Field, _Filter, _FormT, _Validator, _Widget
from wtforms.form import BaseForm
from wtforms.meta import DefaultMeta, _SupportsGettextAndNgettext
class BooleanField(Field):
data: bool
default: bool | Callable[[], bool] | None
false_values: Collection[Any]
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
false_values: Collection[Any] | None = None,
*,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: bool | Callable[[], bool] | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
class StringField(Field):
data: str | None
default: str | Callable[[], str] | None
def __init__(
self,
label: str | None = None,
validators: tuple[_Validator[_FormT, Self], ...] | list[Any] | None = None,
filters: Sequence[_Filter] = (),
description: str = "",
id: str | None = None,
default: str | Callable[[], str] | None = None,
widget: _Widget[Self] | None = None,
render_kw: dict[str, Any] | None = None,
name: str | None = None,
_form: BaseForm | None = None,
_prefix: str = "",
_translations: _SupportsGettextAndNgettext | None = None,
_meta: DefaultMeta | None = None,
) -> None: ...
class TextAreaField(StringField): ...
class PasswordField(StringField): ...
class FileField(Field): ...
class MultipleFileField(FileField):
data: list[Any]
class HiddenField(StringField): ...
class SubmitField(BooleanField): ...
class SearchField(StringField): ...
class TelField(StringField): ...
class URLField(StringField): ...
class EmailField(StringField): ...

View File

@@ -0,0 +1,84 @@
from _typeshed import SupportsItems
from collections.abc import Iterable, Iterator, Mapping, Sequence
from typing import Any, ClassVar, Protocol, overload
from typing_extensions import TypeAlias
from wtforms.fields.core import Field, UnboundField
from wtforms.meta import DefaultMeta, _MultiDictLike
_FormErrors: TypeAlias = dict[str | None, Sequence[str] | _FormErrors]
# _unbound_fields will always be a list on an instance, but on a
# class it might be None, if it never has been instantiated, or
# not instantianted after a new field had been added/removed
class _UnboundFields(Protocol):
@overload
def __get__(self, obj: None, owner: type[object] | None = None) -> list[tuple[str, UnboundField[Any]]] | None: ...
@overload
def __get__(self, obj: object, owner: type[object] | None = None) -> list[tuple[str, UnboundField[Any]]]: ...
class BaseForm:
meta: DefaultMeta
form_errors: list[str]
# we document this, because it's the only efficient way to introspect
# the field names of the form, it also seems to be stable API-wise
_fields: dict[str, Field]
def __init__(
self,
fields: SupportsItems[str, UnboundField[Any]] | Iterable[tuple[str, UnboundField[Any]]],
prefix: str = "",
meta: DefaultMeta = ...,
) -> None: ...
def __iter__(self) -> Iterator[Field]: ...
def __contains__(self, name: str) -> bool: ...
def __getitem__(self, name: str) -> Field: ...
def __setitem__(self, name: str, value: UnboundField[Any]) -> None: ...
def __delitem__(self, name: str) -> None: ...
def populate_obj(self, obj: object) -> None: ...
# while we would like to be more strict on extra_filters, we can't easily do that
# without it being annoying in most situations
def process(
self,
formdata: _MultiDictLike | None = None,
obj: object | None = None,
data: Mapping[str, Any] | None = None,
extra_filters: Mapping[str, Sequence[Any]] | None = None,
**kwargs: object,
) -> None: ...
# same thing here with extra_validators
def validate(self, extra_validators: Mapping[str, Sequence[Any]] | None = None) -> bool: ...
@property
def data(self) -> dict[str, Any]: ...
# because of the Liskov violation in FormField.errors we need to make errors a recursive type
@property
def errors(self) -> _FormErrors: ...
class FormMeta(type):
def __init__(cls, name: str, bases: Sequence[type[object]], attrs: Mapping[str, Any]) -> None: ...
def __call__(cls, *args: Any, **kwargs: Any) -> Any: ...
def __setattr__(cls, name: str, value: object) -> None: ...
def __delattr__(cls, name: str) -> None: ...
class Form(BaseForm, metaclass=FormMeta):
# due to the metaclass this should always be a subclass of DefaultMeta
# but if we annotate this as such, then subclasses cannot use it in the
# intended way
Meta: ClassVar[type[Any]]
# this attribute is documented, so we annotate it
_unbound_fields: _UnboundFields
def __init__(
self,
formdata: _MultiDictLike | None = None,
obj: object | None = None,
prefix: str = "",
data: Mapping[str, Any] | None = None,
meta: Mapping[str, Any] | None = None,
*,
# same issue as with process
extra_filters: Mapping[str, Sequence[Any]] | None = None,
**kwargs: object,
) -> None: ...
# this should emit a type_error, since it's not allowed to be called
def __setitem__(self, name: str, value: None) -> None: ... # type: ignore[override]
def __delitem__(self, name: str) -> None: ...
def __delattr__(self, name: str) -> None: ...

View File

@@ -0,0 +1,30 @@
from collections.abc import Callable, Iterable
from gettext import GNUTranslations
from typing import Protocol, TypeVar, overload
_T = TypeVar("_T")
class _SupportsUgettextAndUngettext(Protocol):
def ugettext(self, __string: str) -> str: ...
def ungettext(self, __singular: str, __plural: str, __n: int) -> str: ...
def messages_path() -> str: ...
def get_builtin_gnu_translations(languages: Iterable[str] | None = None) -> GNUTranslations: ...
@overload
def get_translations(
languages: Iterable[str] | None = None, getter: Callable[[Iterable[str]], GNUTranslations] = ...
) -> GNUTranslations: ...
@overload
def get_translations(languages: Iterable[str] | None = None, *, getter: Callable[[Iterable[str]], _T]) -> _T: ...
@overload
def get_translations(languages: Iterable[str] | None, getter: Callable[[Iterable[str]], _T]) -> _T: ...
class DefaultTranslations:
translations: _SupportsUgettextAndUngettext
def __init__(self, translations: _SupportsUgettextAndUngettext) -> None: ...
def gettext(self, string: str) -> str: ...
def ngettext(self, singular: str, plural: str, n: int) -> str: ...
class DummyTranslations:
def gettext(self, string: str) -> str: ...
def ngettext(self, singular: str, plural: str, n: int) -> str: ...

View File

@@ -0,0 +1,55 @@
from _typeshed import SupportsItems
from collections.abc import Collection, Iterator, MutableMapping
from typing import Any, Protocol, TypeVar, overload
from typing_extensions import Literal, TypeAlias
from markupsafe import Markup
from wtforms.fields.core import Field, UnboundField
from wtforms.form import BaseForm
_FieldT = TypeVar("_FieldT", bound=Field)
class _SupportsGettextAndNgettext(Protocol):
def gettext(self, __string: str) -> str: ...
def ngettext(self, __singular: str, __plural: str, __n: int) -> str: ...
# these are the methods WTForms depends on, the dict can either provide
# a getlist or getall, if it only provies getall, it will wrapped, to
# provide getlist instead
class _MultiDictLikeBase(Protocol):
def __iter__(self) -> Iterator[str]: ...
def __len__(self) -> int: ...
def __contains__(self, __key: Any) -> bool: ...
# since how file uploads are represented in formdata is implementation-specific
# we have to be generous in what we accept in the return of getlist/getall
# we can make this generic if we ever want to be more specific
class _MultiDictLikeWithGetlist(_MultiDictLikeBase, Protocol):
def getlist(self, __key: str) -> list[Any]: ...
class _MultiDictLikeWithGetall(_MultiDictLikeBase, Protocol):
def getall(self, __key: str) -> list[Any]: ...
_MultiDictLike: TypeAlias = _MultiDictLikeWithGetall | _MultiDictLikeWithGetlist
class DefaultMeta:
def bind_field(self, form: BaseForm, unbound_field: UnboundField[_FieldT], options: MutableMapping[str, Any]) -> _FieldT: ...
@overload
def wrap_formdata(self, form: BaseForm, formdata: None) -> None: ...
@overload
def wrap_formdata(self, form: BaseForm, formdata: _MultiDictLike) -> _MultiDictLikeWithGetlist: ...
def render_field(self, field: Field, render_kw: SupportsItems[str, Any]) -> Markup: ...
csrf: bool
csrf_field_name: str
csrf_secret: Any | None
csrf_context: Any | None
csrf_class: type[Any] | None
def build_csrf(self, form: BaseForm) -> Any: ...
locales: Literal[False] | Collection[str]
cache_translations: bool
translations_cache: dict[str, _SupportsGettextAndNgettext]
def get_translations(self, form: BaseForm) -> _SupportsGettextAndNgettext: ...
def update_values(self, values: SupportsItems[str, Any]) -> None: ...
# since meta can be extended with arbitary data we add a __getattr__
# method that returns Any
def __getattr__(self, name: str) -> Any: ...

View File

@@ -0,0 +1,19 @@
from collections.abc import Iterable, Iterator
from typing import Any
from typing_extensions import Literal
from wtforms.meta import _MultiDictLikeWithGetall
def clean_datetime_format_for_strptime(formats: Iterable[str]) -> list[str]: ...
class UnsetValue:
def __bool__(self) -> Literal[False]: ...
unset_value: UnsetValue
class WebobInputWrapper:
def __init__(self, multidict: _MultiDictLikeWithGetall) -> None: ...
def __iter__(self) -> Iterator[str]: ...
def __len__(self) -> int: ...
def __contains__(self, name: str) -> bool: ...
def getlist(self, name: str) -> list[Any]: ...

View File

@@ -0,0 +1,158 @@
from collections.abc import Callable, Collection, Iterable
from decimal import Decimal
from re import Match, Pattern
from typing import Any, TypeVar, overload
from wtforms.fields import Field, StringField
from wtforms.form import BaseForm
_ValuesT = TypeVar("_ValuesT", bound=Collection[Any], contravariant=True)
class ValidationError(ValueError):
def __init__(self, message: str = "", *args: object) -> None: ...
class StopValidation(Exception):
def __init__(self, message: str = "", *args: object) -> None: ...
class EqualTo:
fieldname: str
message: str | None
def __init__(self, fieldname: str, message: str | None = None) -> None: ...
def __call__(self, form: BaseForm, field: Field) -> None: ...
class Length:
min: int
max: int
message: str | None
field_flags: dict[str, Any]
def __init__(self, min: int = -1, max: int = -1, message: str | None = None) -> None: ...
def __call__(self, form: BaseForm, field: StringField) -> None: ...
class NumberRange:
min: float | Decimal | None
max: float | Decimal | None
message: str | None
field_flags: dict[str, Any]
def __init__(
self, min: float | Decimal | None = None, max: float | Decimal | None = None, message: str | None = None
) -> None: ...
# any numeric field will work, for now we don't try to use a union
# to restrict to the defined numeric fields, since user-defined fields
# will likely not use a common base class, just like the existing
# numeric fields
def __call__(self, form: BaseForm, field: Field) -> None: ...
class Optional:
string_check: Callable[[str], bool]
field_flags: dict[str, Any]
def __init__(self, strip_whitespace: bool = True) -> None: ...
def __call__(self, form: BaseForm, field: Field) -> None: ...
class DataRequired:
message: str | None
field_flags: dict[str, Any]
def __init__(self, message: str | None = None) -> None: ...
def __call__(self, form: BaseForm, field: Field) -> None: ...
class InputRequired:
message: str | None
field_flags: dict[str, Any]
def __init__(self, message: str | None = None) -> None: ...
def __call__(self, form: BaseForm, field: Field) -> None: ...
class Regexp:
regex: Pattern[str]
message: str | None
def __init__(self, regex: str | Pattern[str], flags: int = 0, message: str | None = None) -> None: ...
def __call__(self, form: BaseForm, field: StringField, message: str | None = None) -> Match[str]: ...
class Email:
message: str | None
granular_message: bool
check_deliverability: bool
allow_smtputf8: bool
allow_empty_local: bool
def __init__(
self,
message: str | None = None,
granular_message: bool = False,
check_deliverability: bool = False,
allow_smtputf8: bool = True,
allow_empty_local: bool = False,
) -> None: ...
def __call__(self, form: BaseForm, field: StringField) -> None: ...
class IPAddress:
ipv4: bool
ipv6: bool
message: str | None
def __init__(self, ipv4: bool = True, ipv6: bool = False, message: str | None = None) -> None: ...
def __call__(self, form: BaseForm, field: StringField) -> None: ...
@classmethod
def check_ipv4(cls, value: str | None) -> bool: ...
@classmethod
def check_ipv6(cls, value: str | None) -> bool: ...
class MacAddress(Regexp):
def __init__(self, message: str | None = None) -> None: ...
def __call__(self, form: BaseForm, field: StringField) -> None: ... # type: ignore[override]
class URL(Regexp):
validate_hostname: HostnameValidation
def __init__(self, require_tld: bool = True, message: str | None = None) -> None: ...
def __call__(self, form: BaseForm, field: StringField) -> None: ... # type: ignore[override]
class UUID:
message: str | None
def __init__(self, message: str | None = None) -> None: ...
def __call__(self, form: BaseForm, field: StringField) -> None: ...
class AnyOf:
values: Collection[Any]
message: str | None
values_formatter: Callable[[Any], str]
@overload
def __init__(self, values: Collection[Any], message: str | None = None, values_formatter: None = None) -> None: ...
@overload
def __init__(self, values: _ValuesT, message: str | None, values_formatter: Callable[[_ValuesT], str]) -> None: ...
@overload
def __init__(self, values: _ValuesT, message: str | None = None, *, values_formatter: Callable[[_ValuesT], str]) -> None: ...
def __call__(self, form: BaseForm, field: Field) -> None: ...
@staticmethod
def default_values_formatter(values: Iterable[object]) -> str: ...
class NoneOf:
values: Collection[Any]
message: str | None
values_formatter: Callable[[Any], str]
@overload
def __init__(self, values: Collection[Any], message: str | None = None, values_formatter: None = None) -> None: ...
@overload
def __init__(self, values: _ValuesT, message: str | None, values_formatter: Callable[[_ValuesT], str]) -> None: ...
@overload
def __init__(self, values: _ValuesT, message: str | None = None, *, values_formatter: Callable[[_ValuesT], str]) -> None: ...
def __call__(self, form: BaseForm, field: Field) -> None: ...
@staticmethod
def default_values_formatter(v: Iterable[object]) -> str: ...
class HostnameValidation:
hostname_part: Pattern[str]
tld_part: Pattern[str]
require_tld: bool
allow_ip: bool
def __init__(self, require_tld: bool = True, allow_ip: bool = False) -> None: ...
def __call__(self, hostname: str) -> bool: ...
email = Email
equal_to = EqualTo
ip_address = IPAddress
mac_address = MacAddress
length = Length
number_range = NumberRange
optional = Optional
input_required = InputRequired
data_required = DataRequired
regexp = Regexp
url = URL
any_of = AnyOf
none_of = NoneOf

View File

@@ -0,0 +1,2 @@
from wtforms.widgets.core import *
from wtforms.widgets.core import Input as Input, html_params as html_params

View File

@@ -0,0 +1,121 @@
from decimal import Decimal
from typing import Any
from typing_extensions import Literal
from markupsafe import Markup
from wtforms.fields import Field, FormField, SelectFieldBase, StringField
from wtforms.fields.choices import _Option
__all__ = (
"CheckboxInput",
"ColorInput",
"DateInput",
"DateTimeInput",
"DateTimeLocalInput",
"EmailInput",
"FileInput",
"HiddenInput",
"ListWidget",
"MonthInput",
"NumberInput",
"Option",
"PasswordInput",
"RadioInput",
"RangeInput",
"SearchInput",
"Select",
"SubmitInput",
"TableWidget",
"TextArea",
"TextInput",
"TelInput",
"TimeInput",
"URLInput",
"WeekInput",
)
def html_params(**kwargs: object) -> str: ...
class ListWidget:
html_tag: Literal["ul", "ol"]
prefix_label: bool
def __init__(self, html_tag: Literal["ul", "ol"] = "ul", prefix_label: bool = True) -> None: ...
# any iterable field is fine, since people might define iterable fields
# that are not derived from FieldList, we just punt and accept any field
# with Intersection we could be more specific
def __call__(self, field: Field, **kwargs: object) -> Markup: ...
class TableWidget:
with_table_tag: bool
def __init__(self, with_table_tag: bool = True) -> None: ...
def __call__(self, field: FormField[Any], **kwargs: object) -> Markup: ...
class Input:
validation_attrs: list[str]
input_type: str
def __init__(self, input_type: str | None = None) -> None: ...
def __call__(self, field: Field, **kwargs: object) -> Markup: ...
@staticmethod
def html_params(**kwargs: object) -> str: ...
class TextInput(Input): ...
class PasswordInput(Input):
hide_value: bool
def __init__(self, hide_value: bool = True) -> None: ...
class HiddenInput(Input):
field_flags: dict[str, Any]
class CheckboxInput(Input): ...
class RadioInput(Input): ...
class FileInput(Input):
multiple: bool
def __init__(self, multiple: bool = False) -> None: ...
class SubmitInput(Input): ...
class TextArea:
validation_attrs: list[str]
def __call__(self, field: StringField, **kwargs: object) -> Markup: ...
class Select:
validation_attrs: list[str]
multiple: bool
def __init__(self, multiple: bool = False) -> None: ...
def __call__(self, field: SelectFieldBase, **kwargs: object) -> Markup: ...
@classmethod
def render_option(cls, value: object, label: str, selected: bool, **kwargs: object) -> Markup: ...
class Option:
def __call__(self, field: _Option, **kwargs: object) -> Markup: ...
class SearchInput(Input): ...
class TelInput(Input): ...
class URLInput(Input): ...
class EmailInput(Input): ...
class DateTimeInput(Input): ...
class DateInput(Input): ...
class MonthInput(Input): ...
class WeekInput(Input): ...
class TimeInput(Input): ...
class DateTimeLocalInput(Input): ...
class NumberInput(Input):
step: Decimal | float | str | None
min: Decimal | float | str | None
max: Decimal | float | str | None
def __init__(
self,
step: Decimal | float | str | None = None,
min: Decimal | float | str | None = None,
max: Decimal | float | str | None = None,
) -> None: ...
class RangeInput(Input):
# maybe we should allow any str for this
step: Decimal | float | str | None
def __init__(self, step: Decimal | float | str | None = None) -> None: ...
class ColorInput(Input): ...