WTForms 3.3.0b3__tar.gz → 3.3.1__tar.gz

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.
Files changed (181) hide show
  1. {wtforms-3.3.0b3 → wtforms-3.3.1}/CHANGES.rst +101 -94
  2. {wtforms-3.3.0b3 → wtforms-3.3.1}/PKG-INFO +3 -3
  3. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/fields.rst +26 -17
  4. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/forms.rst +2 -0
  5. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/whats_new.rst +16 -1
  6. {wtforms-3.3.0b3 → wtforms-3.3.1}/pyproject.toml +4 -4
  7. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/__init__.py +1 -1
  8. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/datalist.py +24 -15
  9. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/fields/choices.py +79 -30
  10. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/fields/core.py +6 -3
  11. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/fields/datetime.py +22 -25
  12. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/fields/list.py +14 -2
  13. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/fields/numeric.py +18 -16
  14. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/form.py +19 -13
  15. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/wtforms.pot +54 -39
  16. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/validators.py +1 -1
  17. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_date.py +13 -0
  18. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_datetime.py +13 -0
  19. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_datetimelocal.py +4 -4
  20. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_decimal.py +10 -0
  21. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_field.py +9 -0
  22. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_float.py +13 -0
  23. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_form.py +12 -11
  24. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_integer.py +13 -0
  25. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_list.py +105 -0
  26. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_radio.py +17 -0
  27. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_select.py +147 -7
  28. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_selectmultiple.py +29 -0
  29. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_time.py +13 -0
  30. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_week.py +13 -0
  31. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/test_datalist.py +80 -0
  32. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/test_form.py +22 -0
  33. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_url.py +8 -0
  34. {wtforms-3.3.0b3 → wtforms-3.3.1}/.gitignore +0 -0
  35. {wtforms-3.3.0b3 → wtforms-3.3.1}/LICENSE.rst +0 -0
  36. {wtforms-3.3.0b3 → wtforms-3.3.1}/README.rst +0 -0
  37. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/Makefile +0 -0
  38. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/_static/logo_joined.svg +0 -0
  39. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/_static/logo_split.svg +0 -0
  40. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/_static/logo_square.svg +0 -0
  41. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/changes.rst +0 -0
  42. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/conf.py +0 -0
  43. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/contributing.rst +0 -0
  44. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/crash_course.rst +0 -0
  45. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/csrf.rst +0 -0
  46. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/ext.rst +0 -0
  47. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/faq.rst +0 -0
  48. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/i18n.rst +0 -0
  49. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/index.rst +0 -0
  50. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/license.rst +0 -0
  51. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/make.bat +0 -0
  52. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/meta.rst +0 -0
  53. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/specific_problems.rst +0 -0
  54. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/validators.rst +0 -0
  55. {wtforms-3.3.0b3 → wtforms-3.3.1}/docs/widgets.rst +0 -0
  56. {wtforms-3.3.0b3 → wtforms-3.3.1}/hatch_build.py +0 -0
  57. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/_compat.py +0 -0
  58. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/csrf/__init__.py +0 -0
  59. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/csrf/core.py +0 -0
  60. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/csrf/session.py +0 -0
  61. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/fields/__init__.py +0 -0
  62. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/fields/form.py +0 -0
  63. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/fields/simple.py +0 -0
  64. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/i18n.py +0 -0
  65. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/README.md +0 -0
  66. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.mo +0 -0
  67. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.po +0 -0
  68. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.mo +0 -0
  69. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.po +0 -0
  70. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.mo +0 -0
  71. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.po +0 -0
  72. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.mo +0 -0
  73. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.po +0 -0
  74. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.mo +0 -0
  75. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.po +0 -0
  76. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/de/LC_MESSAGES/wtforms.mo +0 -0
  77. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/de/LC_MESSAGES/wtforms.po +0 -0
  78. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.mo +0 -0
  79. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.po +0 -0
  80. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/el/LC_MESSAGES/wtforms.mo +0 -0
  81. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/el/LC_MESSAGES/wtforms.po +0 -0
  82. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/en/LC_MESSAGES/wtforms.mo +0 -0
  83. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/en/LC_MESSAGES/wtforms.po +0 -0
  84. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/es/LC_MESSAGES/wtforms.mo +0 -0
  85. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/es/LC_MESSAGES/wtforms.po +0 -0
  86. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/et/LC_MESSAGES/wtforms.mo +0 -0
  87. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/et/LC_MESSAGES/wtforms.po +0 -0
  88. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.mo +0 -0
  89. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.po +0 -0
  90. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.mo +0 -0
  91. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.po +0 -0
  92. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.mo +0 -0
  93. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.po +0 -0
  94. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/he/LC_MESSAGES/wtforms.mo +0 -0
  95. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/he/LC_MESSAGES/wtforms.po +0 -0
  96. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.mo +0 -0
  97. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.po +0 -0
  98. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/it/LC_MESSAGES/wtforms.mo +0 -0
  99. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/it/LC_MESSAGES/wtforms.po +0 -0
  100. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.mo +0 -0
  101. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.po +0 -0
  102. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.mo +0 -0
  103. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.po +0 -0
  104. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.mo +0 -0
  105. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.po +0 -0
  106. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.mo +0 -0
  107. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.po +0 -0
  108. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.mo +0 -0
  109. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.po +0 -0
  110. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.mo +0 -0
  111. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.po +0 -0
  112. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.mo +0 -0
  113. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.po +0 -0
  114. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.mo +0 -0
  115. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.po +0 -0
  116. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.mo +0 -0
  117. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.po +0 -0
  118. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.mo +0 -0
  119. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.po +0 -0
  120. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/sr/LC_MESSAGES/wtforms.mo +0 -0
  121. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/sr/LC_MESSAGES/wtforms.po +0 -0
  122. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.mo +0 -0
  123. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.po +0 -0
  124. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.mo +0 -0
  125. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.po +0 -0
  126. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.mo +0 -0
  127. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.po +0 -0
  128. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.mo +0 -0
  129. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.po +0 -0
  130. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.mo +0 -0
  131. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.po +0 -0
  132. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.mo +0 -0
  133. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.po +0 -0
  134. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/meta.py +0 -0
  135. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/utils.py +0 -0
  136. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/widgets/__init__.py +0 -0
  137. {wtforms-3.3.0b3 → wtforms-3.3.1}/src/wtforms/widgets/core.py +0 -0
  138. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/__init__.py +0 -0
  139. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/common.py +0 -0
  140. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/conftest.py +0 -0
  141. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/__init__.py +0 -0
  142. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_boolean.py +0 -0
  143. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_button.py +0 -0
  144. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_color.py +0 -0
  145. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_custom.py +0 -0
  146. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_defaults.py +0 -0
  147. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_file.py +0 -0
  148. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_filters.py +0 -0
  149. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_flags.py +0 -0
  150. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_hidden.py +0 -0
  151. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_html5.py +0 -0
  152. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_label.py +0 -0
  153. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_month.py +0 -0
  154. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_password.py +0 -0
  155. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_string.py +0 -0
  156. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_submit.py +0 -0
  157. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/fields/test_textarea.py +0 -0
  158. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/test_csrf.py +0 -0
  159. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/test_i18n.py +0 -0
  160. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/test_locale_babel.py +0 -0
  161. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/test_webob.py +0 -0
  162. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/test_widgets.py +0 -0
  163. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/__init__.py +0 -0
  164. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_anyof.py +0 -0
  165. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_data_required.py +0 -0
  166. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_date_range.py +0 -0
  167. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_disabled.py +0 -0
  168. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_email.py +0 -0
  169. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_equal_to.py +0 -0
  170. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_input_required.py +0 -0
  171. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_ipaddress.py +0 -0
  172. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_lazy_proxy.py +0 -0
  173. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_length.py +0 -0
  174. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_mac_address.py +0 -0
  175. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_noneof.py +0 -0
  176. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_number_range.py +0 -0
  177. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_optional.py +0 -0
  178. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_readonly.py +0 -0
  179. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_regexp.py +0 -0
  180. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_uuid.py +0 -0
  181. {wtforms-3.3.0b3 → wtforms-3.3.1}/tests/validators/test_validators.py +0 -0
@@ -1,118 +1,125 @@
1
1
  .. currentmodule:: wtforms
2
2
 
3
- Version 3.3.0b3
4
- ---------------
3
+ Version 3.3.1
4
+ -------------
5
5
 
6
- Released 2026-06-12
6
+ Released 2026-10-24
7
7
 
8
- - Add :func:`fields.enum_choices`, :func:`fields.enum_coerce` and
9
- :func:`~wtforms.datalist.enum_datalist` for Enum-backed choices,
10
- replacing ``SelectChoice.from_enum`` and friends. :issue:`922`
11
- - An Enum ``coerce`` is no longer wrapped, and options default to
12
- ``member.value``. :issue:`922`
8
+ - Fix :class:`~fields.FieldList` entries losing the form prefix in their
9
+ ``name`` when the list renumbers them. :issue:`944`
10
+ - Speed up :class:`~validators.URL` on hostnames with many consecutive
11
+ dashes. :issue:`945`
13
12
 
14
- Version 3.3.0b2
15
- ---------------
13
+ Version 3.3.0
14
+ -------------
16
15
 
17
- Released 2026-05-24
16
+ Released 2026-10-23
18
17
 
19
- - :class:`~fields.SelectField` ``choices`` accepts a shorthand dict
20
- syntax: ``{value: label}`` for flat options and
21
- ``{label: {value: label}}`` for optgroups. :issue:`886`
22
- - Split choice types by role: :class:`~fields.SelectChoice` and
23
- :class:`~fields.DataListChoice` to declare options via ``choices=``,
24
- :class:`~fields.Choice` for what ``iter_choices`` / ``iter_groups``
25
- yield. :issue:`922`
18
+ - Drop support for Python 3.10. Add support for Python 3.15.
19
+ - Add native :class:`DataList` support. Options are declared with
20
+ :class:`DataListChoice`, or with the shorthand ``{value: label}``
21
+ dict syntax. :issue:`653` :issue:`886` :pr:`921`
22
+ - Add :class:`~fields.ButtonField` to render ``<button type="submit">``
23
+ controls with string values. :issue:`784` :pr:`892`
24
+ - Add :class:`~validators.DateRange`. ``min`` and ``max`` may be callables
25
+ to compute dynamic bounds; :class:`~validators.NumberRange` accepts
26
+ callables on the same terms. :pr:`787`
27
+ - Add :class:`~fields.FieldList` ``insert_entry`` and indexed ``pop_entry``.
28
+ The following entries are renumbered, with their ids and labels.
29
+ :issue:`256` :pr:`901` :pr:`912`
30
+ - :class:`~fields.SelectField` refactor. Options are declared with
31
+ :class:`~fields.SelectChoice`, which takes HTML attributes in
32
+ ``render_kw`` and an ``optgroup``. ``iter_choices``
33
+ and ``iter_groups`` yield
34
+ :class:`~fields.Choice` objects. ``choices`` also accepts the shorthand
35
+ dict syntax: ``{value: label}`` for flat options and
36
+ ``{label: {value: label}}`` for optgroups. :issue:`692` :issue:`886`
37
+ :issue:`922` :pr:`739` :pr:`889`
26
38
  - ``choices`` callables on :class:`~fields.SelectField` and
27
- :class:`~datalist.DataList` may accept ``(form, field)``. Resolved
28
- once per processing cycle. :issue:`922`
39
+ :class:`DataList` may accept ``(form, field)``. They run on the
40
+ first read and their result is cached. Calling
41
+ :meth:`~form.BaseForm.post_process` again refreshes them, also for the
42
+ entries added with :meth:`~fields.FieldList.append_entry` and
43
+ :meth:`~fields.FieldList.insert_entry`. :issue:`922`
44
+ - Add :func:`fields.enum_choices`, :func:`fields.enum_coerce` and
45
+ :func:`~datalist.enum_datalist` for Enum-backed choices. :issue:`338`
46
+ :issue:`922` :pr:`916`
29
47
  - Add :meth:`fields.Field.post_process` and
30
48
  :meth:`form.BaseForm.post_process` hooks, propagated through
31
- :class:`~fields.FormField` and :class:`~fields.FieldList` for
49
+ :class:`~fields.FormField` and :class:`~fields.FieldList`, for
32
50
  cross-field finalization. :issue:`922`
33
51
  - Add ``Field._form`` and ``BaseForm._parent_form`` so fields and nested
34
- forms can reach their enclosing form. Fix :class:`~fields.FieldList`
35
- entries and :class:`~fields.SelectField` option subfields which
36
- previously got ``form=None``. :issue:`922`
37
- - Restore 3.2 compatibility for :class:`~fields.SelectField` subclasses:
38
- tuple yields from :meth:`~fields.SelectFieldBase.iter_choices` /
39
- :meth:`~fields.SelectFieldBase.iter_groups`,
40
- 3-positional :meth:`widgets.Select.render_option` overrides,
41
- :meth:`~fields.SelectFieldBase.has_groups` /
42
- :meth:`~fields.SelectFieldBase.iter_groups`, and ``self.choices``
43
- keeping the user-supplied shape. Each emits a ``DeprecationWarning``;
44
- will be removed in WTForms 4.0. :issue:`922`
45
-
46
- Version 3.3.0b1
47
- ---------------
48
-
49
- Released 2026-05-10
50
-
51
- - Add :class:`~validators.DateRange`. ``min`` and ``max`` may be callables
52
- to compute dynamic bounds; :class:`~validators.NumberRange` accepts
53
- callables on the same terms. :pr:`787`
52
+ forms can reach their enclosing form. :class:`~fields.FieldList` entries
53
+ and :class:`~fields.SelectField` option subfields no longer get
54
+ ``form=None``. :issue:`922`
55
+ - Add ``invalid_value_message`` and ``invalid_choice_message`` keyword
56
+ arguments for customizing built-in field error messages. On
57
+ :class:`~fields.SelectMultipleField`, ``invalid_choice_message`` may also
58
+ be a callable for custom pluralization. :issue:`39` :issue:`832` :pr:`902`
59
+ - :class:`~fields.SelectField` and :class:`~fields.SelectMultipleField`
60
+ ``pre_validate`` short-circuits when ``process_formdata`` already produced
61
+ an error, so a single coercion failure no longer surfaces as both an
62
+ "invalid value" and an "invalid choice" error. :pr:`902`
63
+ - Numeric, date, time and select fields report a validation error instead of
64
+ raising a :class:`TypeError` when the input is not a string. :pr:`936`
65
+ - Each bound field gets its own copy of ``render_kw``, and so does its
66
+ :class:`DataList`. Changing one of them on a form instance no
67
+ longer changes it on the other ones. :issue:`942`
54
68
  - Make :class:`~fields.FloatField` render as ``<input type="number">`` with
55
69
  ``step="any"``. :issue:`677` :pr:`679`
56
- - :meth:`~fields.FieldList._add_entry` now routes through :meth:`~meta.DefaultMeta.bind_field`,
57
- consistent with how the form constructor binds top-level fields. Custom ``Meta.bind_field``
58
- overrides were silently bypassed for all ``FieldList`` entries. :pr:`891`
59
- - Add `test_environment` support to :class:`~validators.Email`. :issue:`869` :pr:`894`
60
- - Reset :class:`~fields.FieldList` index state when reprocessing entries. :issue:`874` :pr:`895`
61
- - Let :class:`~widgets.RangeInput` accept `min` and `max` at construction time. :issue:`693` :pr:`898`
62
- - Do not render a ``for`` attribute on the main label of :class:`~fields.RadioField`.
63
- :issue:`775` :pr:`900`
64
- - Deprecate :class:`~fields.DateTimeField`, which will be removed in WTForms 3.4. :issue:`831` :pr:`897`
65
- - Clarify that :class:`~fields.SelectField` ``choices`` is re-evaluated at
66
- every form instantiation, including callables, in the documentation. :issue:`875` :pr:`909`
67
- - Render ``False`` as ``value="False"`` in :class:`~fields.SelectField` options
68
- instead of omitting the attribute. :issue:`878` :pr:`908`
69
- - Accept :class:`uuid.UUID` instances in the :class:`~validators.UUID` validator,
70
- and translate unexpected types into :class:`~validators.ValidationError`
71
- instead of letting :class:`TypeError` escape. :issue:`549` :pr:`769`
72
- - Escape double quotes in :class:`~markupsafe.Markup` attribute values
73
- passed to :func:`~widgets.html_params`, and return a
74
- :class:`~markupsafe.Markup`. :issue:`801` :pr:`906` :pr:`907`
75
- - Clarify documentation about filters. :issue:`859` :pr:`896`
70
+ - Add a ``tz`` parameter to :class:`~fields.DateTimeLocalField` to associate a
71
+ timezone with the input. :issue:`833` :pr:`905`
72
+ - Let :class:`~widgets.RangeInput` accept ``min`` and ``max`` at construction
73
+ time. :issue:`693` :pr:`898`
74
+ - ``FieldList._add_entry`` routes through
75
+ :meth:`~meta.DefaultMeta.bind_field`, like the top-level fields. Custom
76
+ ``Meta.bind_field`` overrides were silently bypassed for
77
+ :class:`~fields.FieldList` entries. :pr:`891`
78
+ - Reset :class:`~fields.FieldList` index state when reprocessing entries.
79
+ :issue:`874` :pr:`895`
80
+ - Do not render a ``for`` attribute on the main label of
81
+ :class:`~fields.RadioField`. :issue:`775` :pr:`900`
82
+ - Render ``False`` as ``value="False"`` in :class:`~fields.SelectField`
83
+ options instead of omitting the attribute. :issue:`878` :pr:`908`
76
84
  - Let render-time ``checked`` kwargs take precedence over the field's value
77
85
  in :class:`~widgets.CheckboxInput` and :class:`~widgets.RadioInput`.
78
86
  :issue:`706` :pr:`910`
79
- - Add a ``tz`` parameter to :class:`~fields.DateTimeLocalField` to associate a
80
- timezone with the input. :issue:`833` :pr:`905`
81
- - Add :class:`~fields.FieldList` ``insert_entry`` and indexed ``pop_entry``.
82
- :issue:`256` :pr:`901` :pr:`912`
83
- - Add :class:`~fields.ButtonField` to render ``<button type="submit">``
84
- controls with string values. :issue:`784` :pr:`892`
85
- - :class:`~fields.SelectField` refactor. Choices tuples and dicts are
86
- deprecated in favor of :class:`~fields.Choice`. :pr:`739` :pr:`889`
87
- - ``<option>`` HTML attributes can be passed using
88
- :class:`~fields.Choice`. :issue:`692` :pr:`739` :pr:`889`
89
- - Add :meth:`fields.Choice.from_enum` to build choices from an
90
- :class:`enum.Enum` class, and let :class:`~fields.SelectField` accept an
91
- Enum class as ``coerce``. :issue:`338` :pr:`916`
92
- - Defer to ``email_validator``'s module-level defaults for the
93
- :class:`~validators.Email` ``test_environment``, ``allow_smtputf8`` and
94
- ``allow_empty_local`` options. :issue:`915` :pr:`917`
95
- - Add ``invalid_value_message`` and ``invalid_choice_message`` keyword
96
- arguments for customizing built-in field error messages. On
97
- ``SelectMultipleField``, ``invalid_choice_message`` may also be a callable
98
- for custom pluralization. :issue:`39` :issue:`832` :pr:`902`
99
- - :class:`~fields.SelectField` and :class:`~fields.SelectMultipleField`
100
- ``pre_validate`` now short-circuits when ``process_formdata`` already
101
- produced an error, so a single coercion failure no longer surfaces as both
102
- an "invalid value" and an "invalid choice" error. :pr:`902`
103
- - Add a ``matcher`` keyword argument to :class:`~validators.Regexp` to
104
- override the matching function. Defaults to :func:`re.match`; pass
105
- :func:`re.search` or :func:`re.fullmatch` to change the anchoring
106
- behaviour. :issue:`867` :pr:`918`
107
- - Add an ``html_pattern`` keyword argument to :class:`~validators.Regexp`
108
- to control the HTML ``pattern`` attribute emitted on supporting widgets.
109
- :pr:`759`
87
+ - Escape double quotes in ``Markup`` attribute values
88
+ passed to :func:`~widgets.html_params`, and return a
89
+ ``Markup``. :issue:`801` :pr:`906` :pr:`907`
90
+ - Add ``test_environment`` support to :class:`~validators.Email`, and defer
91
+ to ``email_validator``'s module-level defaults for its
92
+ ``test_environment``, ``allow_smtputf8`` and ``allow_empty_local``
93
+ options. :issue:`869` :issue:`915` :pr:`894` :pr:`917`
94
+ - Add ``matcher`` and ``html_pattern`` keyword arguments to
95
+ :class:`~validators.Regexp`. ``matcher`` overrides the matching function,
96
+ :func:`re.match` by default: pass :func:`re.search` or :func:`re.fullmatch`
97
+ to change the anchoring. ``html_pattern`` controls the HTML ``pattern``
98
+ attribute emitted on supporting widgets. :issue:`867` :pr:`759` :pr:`918`
110
99
  - :class:`~validators.URL` validator is based on ``urllib.parse.urlparse``
111
100
  instead of a regex. New keyword arguments: ``allow_userinfo`` for
112
101
  accepting ``user:password@host`` URLs; ``schemes``
113
- (default ``("http", "https")``) is the allowlist of accepted schemes
102
+ (default ``("http", "https")``) is the allowlist of accepted schemes.
114
103
  :pr:`847`
115
- - Add native :class:`DataList` support. :issue:`653` :pr:`921`
104
+ - Accept :class:`uuid.UUID` instances in the :class:`~validators.UUID`
105
+ validator, and translate unexpected types into
106
+ :class:`~validators.ValidationError` instead of letting
107
+ :class:`TypeError` escape. :issue:`549` :pr:`769`
108
+ - Clarify the documentation of filters, and of :class:`~fields.SelectField`
109
+ ``choices``. :issue:`859` :issue:`875` :pr:`896` :pr:`909`
110
+ - Update the Dutch and Serbian translations.
111
+ - Deprecate :class:`~fields.DateTimeField`, which will be removed in WTForms
112
+ 3.4. :issue:`831` :pr:`897`
113
+ - Passing :class:`~fields.SelectField` ``choices`` as tuples, or as a dict
114
+ that is not the shorthand syntax, is deprecated and will be removed in
115
+ WTForms 3.4. Use :class:`~fields.SelectChoice` instead. :issue:`922`
116
+ - Passing a callable to ``SelectFieldBase._choices_from_input`` is
117
+ deprecated and will be removed in WTForms 3.4.
118
+ - For :class:`~fields.SelectField` subclasses, yielding raw tuples from
119
+ ``iter_choices`` or
120
+ ``iter_groups``, and overriding
121
+ ``Select.render_option`` with the pre-3.3 signature, are
122
+ deprecated and will be removed in WTForms 4.0. :issue:`922`
116
123
 
117
124
  Version 3.2.2
118
125
  -------------
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: WTForms
3
- Version: 3.3.0b3
3
+ Version: 3.3.1
4
4
  Summary: Form validation and rendering for Python web development.
5
5
  Project-URL: Documentation, https://wtforms.readthedocs.io
6
6
  Project-URL: Changes, https://wtforms.readthedocs.io/changes
@@ -44,7 +44,7 @@ Classifier: License :: OSI Approved :: BSD License
44
44
  Classifier: Operating System :: OS Independent
45
45
  Classifier: Programming Language :: Python
46
46
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
47
- Requires-Python: >=3.10
47
+ Requires-Python: >=3.11
48
48
  Requires-Dist: markupsafe>=1.1.1
49
49
  Provides-Extra: email
50
50
  Requires-Dist: email-validator>=2.0; extra == 'email'
@@ -67,6 +67,7 @@ The Field base class
67
67
  .. automethod:: process(formdata [, data])
68
68
  .. automethod:: process_data
69
69
  .. automethod:: process_formdata
70
+ .. automethod:: post_process
70
71
  .. attribute:: data
71
72
 
72
73
  Contains the resulting (sanitized) value of calling either of the
@@ -329,8 +330,8 @@ Choice Fields
329
330
  SelectChoice('text', 'Plain Text'),
330
331
  ])
331
332
 
332
- Note that the `choices` keyword is evaluated each time the form is
333
- instantiated, so callables passed as `choices` are re-evaluated per instance.
333
+ A callable passed as `choices` is resolved separately for each form
334
+ instance, the first time its choices are read.
334
335
  For dynamic options, either pass a callable to `choices` or assign
335
336
  `field.choices` after instantiation. Any submitted choices which are not in
336
337
  the given choices list will cause validation on the field to fail. If this
@@ -361,17 +362,22 @@ Choice Fields
361
362
  user = User.query.get(id)
362
363
  form = UserDetails(request.POST, obj=user)
363
364
 
364
- Note we passed a callable to `choices` rather than a static list; it is
365
- invoked each time the form is instantiated, producing fresh options per
366
- request. If the choices depend on data only available in the view, you can
367
- instead assign `form.group_id.choices` after instantiation. The `coerce`
368
- keyword arg to :class:`~wtforms.fields.SelectField` says that we use
369
- :func:`int()` to coerce form data. The default coerce is :func:`str()`.
365
+ Note we passed a callable to `choices` rather than a static list; it
366
+ produces fresh options per request. Its result is cached. Calling
367
+ :meth:`~wtforms.form.BaseForm.post_process` again refreshes it.
368
+
369
+ If the choices depend on data only available in the view, you can instead
370
+ assign `form.group_id.choices` after instantiation. A list assigned this
371
+ way replaces the callable.
372
+
373
+ The `coerce` keyword arg to :class:`~wtforms.fields.SelectField` says
374
+ that we use :func:`int()` to coerce form data. The default coerce is
375
+ :func:`str()`.
370
376
 
371
377
  The callable may optionally accept ``(form, field)`` as positional
372
- arguments. When this signature is used, the callable is invoked after the
373
- whole form has been processed, so it can read ``field.data`` and the data
374
- of any other field on the form::
378
+ arguments. The callable is invoked when the choices are read, normally
379
+ after the whole form has been processed, so it can read ``field.data`` and
380
+ the data of any other field on the form::
375
381
 
376
382
  def available_groups(form, field):
377
383
  return [SelectChoice(g.id, g.name) for g in form.tenant.data.groups]
@@ -610,12 +616,15 @@ Data Lists
610
616
 
611
617
  ``choices`` is either a list of :class:`~wtforms.DataListChoice`
612
618
  (or plain strings, in which case the string is used as both value
613
- and label), or a callable invoked at render time. The callable
614
- may take no argument (``fn()``) for a static list, or ``(field)``
615
- to adapt the suggestions to the current field value at each
616
- render — convenient for server-side filtering::
619
+ and label), a shorthand ``{value: label}`` dict, or a callable. The
620
+ callable runs when the datalist is rendered, after the whole form has been
621
+ processed. Its result is cached until the next
622
+ :meth:`~wtforms.form.BaseForm.post_process`. The callable may
623
+ take no argument (``fn()``) for a static list, or ``(form, field)``
624
+ to adapt the suggestions to the form data, for example the current
625
+ field value. This is convenient for server-side filtering::
617
626
 
618
- DataList(lambda field: search(field.data))
627
+ DataList(lambda form, field: search(field.data))
619
628
 
620
629
  **Inline DataList owned by a field**
621
630
 
@@ -642,7 +651,7 @@ Data Lists
642
651
  class F(Form):
643
652
  items = FieldList(
644
653
  StringField(
645
- datalist=DataList(lambda field: suggest(field.data))
654
+ datalist=DataList(lambda form, field: suggest(field.data))
646
655
  ),
647
656
  min_entries=3,
648
657
  )
@@ -294,6 +294,8 @@ fields.
294
294
  this to provide form data to the enclosed fields. Accessing the field's
295
295
  data before calling process is not recommended.
296
296
 
297
+ .. automethod:: post_process
298
+
297
299
  .. automethod:: validate
298
300
 
299
301
  .. automethod:: __iter__
@@ -4,7 +4,22 @@ What's New in WTForms 3
4
4
  New Features
5
5
  ------------
6
6
 
7
- WTForms 3 is something something TODO
7
+ WTForms 3.3 adds:
8
+
9
+ - native :class:`~wtforms.DataList` support, to suggest values in a text
10
+ input;
11
+ - :class:`~wtforms.fields.ButtonField`, to render ``<button type="submit">``
12
+ controls;
13
+ - the :class:`~wtforms.validators.DateRange` validator;
14
+ - :meth:`~wtforms.fields.FieldList.insert_entry` and an indexed
15
+ :meth:`~wtforms.fields.FieldList.pop_entry`;
16
+ - a reworked :class:`~wtforms.fields.SelectField`: options are declared with
17
+ :class:`~wtforms.fields.SelectChoice`, which takes HTML attributes and an
18
+ optgroup, and ``choices`` callables run on the first read;
19
+ - :meth:`~wtforms.form.BaseForm.post_process` hooks for cross-field
20
+ finalization.
21
+
22
+ See :doc:`changes` for the full list, including the deprecations.
8
23
 
9
24
 
10
25
  Past Major Releases
@@ -13,7 +13,7 @@ classifiers = [
13
13
  "Programming Language :: Python",
14
14
  "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
15
15
  ]
16
- requires-python = ">=3.10"
16
+ requires-python = ">=3.11"
17
17
  dependencies = [
18
18
  "MarkupSafe>=1.1.1",
19
19
  ]
@@ -49,12 +49,12 @@ dev = [
49
49
  env_list = [
50
50
  "style",
51
51
  "minversions",
52
+ "py315",
52
53
  "py314",
53
54
  "py313",
54
55
  "py312",
55
56
  "py311",
56
- "py310",
57
- "pypy310",
57
+ "pypy311",
58
58
  "docs",
59
59
  ]
60
60
  skip_missing_interpreters = true
@@ -86,7 +86,7 @@ dependency_groups = ["docs"]
86
86
  commands = [["sphinx-build", "-W", "-b", "html", "-d", "{envtmpdir}/doctrees", "docs", "{envtmpdir}/html"]]
87
87
 
88
88
  [tool.tox.env.minversions]
89
- base_python = ["py3.10"]
89
+ base_python = ["py3.11"]
90
90
  uv_resolution = "lowest-direct"
91
91
  dependency_groups = ["dev"]
92
92
  commands = [
@@ -42,7 +42,7 @@ from wtforms.fields.simple import URLField
42
42
  from wtforms.form import Form
43
43
  from wtforms.validators import ValidationError
44
44
 
45
- __version__ = "3.3.0b3"
45
+ __version__ = "3.3.1"
46
46
 
47
47
  __all__ = [
48
48
  "validators",
@@ -1,10 +1,11 @@
1
1
  import warnings
2
2
  from dataclasses import dataclass
3
3
  from dataclasses import field
4
+ from dataclasses import replace
4
5
 
5
6
  from wtforms import widgets
6
- from wtforms._compat import get_signature
7
7
  from wtforms.fields.choices import _enum_options
8
+ from wtforms.fields.choices import _invoke_choices_callable
8
9
  from wtforms.fields.choices import Choice
9
10
 
10
11
  __all__ = ("DataList", "DataListChoice", "enum_datalist")
@@ -70,7 +71,8 @@ def enum_datalist(enum_cls, *, by="value", label=None):
70
71
  Same semantics as :func:`~wtforms.fields.enum_choices`: ``by`` selects
71
72
  which member attribute becomes the ``<option>`` value (``"value"`` by
72
73
  default, ``"name"`` otherwise) and ``label`` defaults to ``str(member)``
73
- when the Enum defines its own ``__str__``, else ``member.name``.
74
+ when the Enum defines its own ``__str__`` or is a :class:`enum.StrEnum`,
75
+ else ``member.name``.
74
76
 
75
77
  A ``<datalist>`` only suggests values for a free-text field, so there is
76
78
  no coercion counterpart — the field stores whatever string is submitted.
@@ -91,6 +93,7 @@ class DataList:
91
93
  def __init__(self, choices=None, *, render_kw=None, widget=None):
92
94
  self._raw_choices = choices
93
95
  self._choices = None if callable(choices) else choices
96
+ self._field = None
94
97
  self.render_kw = render_kw or {}
95
98
  if widget is not None:
96
99
  self.widget = widget
@@ -100,28 +103,34 @@ class DataList:
100
103
  clone = DataList.__new__(DataList)
101
104
  clone._raw_choices = self._raw_choices
102
105
  clone._choices = None if callable(self._raw_choices) else self._raw_choices
103
- clone.render_kw = self.render_kw
106
+ clone._field = None
107
+ clone.render_kw = dict(self.render_kw)
104
108
  clone.widget = self.widget
105
109
  clone.id = id
106
110
  return clone
107
111
 
108
- def _resolve(self, field):
109
- raw = self._raw_choices
110
- if not callable(raw):
111
- return
112
- try:
113
- sig = get_signature(raw)
114
- sig.bind(field._form, field)
115
- except TypeError:
116
- self._choices = raw()
117
- return
118
- self._choices = raw(field._form, field)
112
+ def _invalidate(self, field):
113
+ """Mark the choices for a lazy refresh on the next :meth:`iter_choices`."""
114
+ if callable(self._raw_choices):
115
+ self._field = field
119
116
 
120
117
  def iter_choices(self, field=None):
118
+ if (bound := self._field) is not None:
119
+ self._field = None
120
+ self._choices = _invoke_choices_callable(
121
+ self._raw_choices, bound._form, bound
122
+ )
121
123
  raw = self._choices
122
124
  if raw is None:
123
125
  return []
124
- return [DataListChoice.from_input(item) for item in raw]
126
+ if isinstance(raw, dict):
127
+ return [
128
+ DataListChoice(value=value, label=label) for value, label in raw.items()
129
+ ]
130
+ return [
131
+ replace(choice, render_kw=dict(choice.render_kw or {}))
132
+ for choice in map(DataListChoice.from_input, raw)
133
+ ]
125
134
 
126
135
  def __call__(self, field=None, **kwargs):
127
136
  return self.widget(self, field=field, **kwargs)