WTForms 3.3.0b2__tar.gz → 3.3.0b4__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.0b2 → wtforms-3.3.0b4}/CHANGES.rst +39 -0
  2. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/PKG-INFO +3 -3
  3. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/fields.rst +66 -36
  4. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/forms.rst +2 -0
  5. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/pyproject.toml +4 -4
  6. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/__init__.py +3 -1
  7. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/datalist.py +33 -23
  8. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/fields/__init__.py +4 -0
  9. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/fields/choices.py +132 -57
  10. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/fields/core.py +9 -3
  11. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/fields/datetime.py +22 -25
  12. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/fields/form.py +2 -2
  13. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/fields/list.py +15 -3
  14. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/fields/numeric.py +18 -16
  15. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/form.py +24 -13
  16. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_date.py +13 -0
  17. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_datetime.py +13 -0
  18. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_datetimelocal.py +4 -4
  19. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_decimal.py +10 -0
  20. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_float.py +13 -0
  21. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_form.py +18 -17
  22. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_integer.py +13 -0
  23. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_list.py +77 -0
  24. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_radio.py +17 -0
  25. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_select.py +276 -44
  26. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_selectmultiple.py +72 -6
  27. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_time.py +13 -0
  28. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_week.py +13 -0
  29. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/test_datalist.py +76 -0
  30. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/test_form.py +22 -0
  31. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/.gitignore +0 -0
  32. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/LICENSE.rst +0 -0
  33. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/README.rst +0 -0
  34. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/Makefile +0 -0
  35. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/_static/logo_joined.svg +0 -0
  36. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/_static/logo_split.svg +0 -0
  37. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/_static/logo_square.svg +0 -0
  38. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/changes.rst +0 -0
  39. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/conf.py +0 -0
  40. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/contributing.rst +0 -0
  41. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/crash_course.rst +0 -0
  42. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/csrf.rst +0 -0
  43. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/ext.rst +0 -0
  44. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/faq.rst +0 -0
  45. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/i18n.rst +0 -0
  46. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/index.rst +0 -0
  47. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/license.rst +0 -0
  48. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/make.bat +0 -0
  49. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/meta.rst +0 -0
  50. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/specific_problems.rst +0 -0
  51. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/validators.rst +0 -0
  52. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/whats_new.rst +0 -0
  53. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/docs/widgets.rst +0 -0
  54. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/hatch_build.py +0 -0
  55. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/_compat.py +0 -0
  56. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/csrf/__init__.py +0 -0
  57. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/csrf/core.py +0 -0
  58. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/csrf/session.py +0 -0
  59. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/fields/simple.py +0 -0
  60. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/i18n.py +0 -0
  61. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/README.md +0 -0
  62. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.mo +0 -0
  63. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.po +0 -0
  64. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.mo +0 -0
  65. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.po +0 -0
  66. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.mo +0 -0
  67. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.po +0 -0
  68. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.mo +0 -0
  69. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.po +0 -0
  70. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.mo +0 -0
  71. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.po +0 -0
  72. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/de/LC_MESSAGES/wtforms.mo +0 -0
  73. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/de/LC_MESSAGES/wtforms.po +0 -0
  74. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.mo +0 -0
  75. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.po +0 -0
  76. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/el/LC_MESSAGES/wtforms.mo +0 -0
  77. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/el/LC_MESSAGES/wtforms.po +0 -0
  78. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/en/LC_MESSAGES/wtforms.mo +0 -0
  79. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/en/LC_MESSAGES/wtforms.po +0 -0
  80. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/es/LC_MESSAGES/wtforms.mo +0 -0
  81. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/es/LC_MESSAGES/wtforms.po +0 -0
  82. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/et/LC_MESSAGES/wtforms.mo +0 -0
  83. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/et/LC_MESSAGES/wtforms.po +0 -0
  84. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.mo +0 -0
  85. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.po +0 -0
  86. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.mo +0 -0
  87. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.po +0 -0
  88. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.mo +0 -0
  89. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.po +0 -0
  90. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/he/LC_MESSAGES/wtforms.mo +0 -0
  91. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/he/LC_MESSAGES/wtforms.po +0 -0
  92. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.mo +0 -0
  93. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.po +0 -0
  94. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/it/LC_MESSAGES/wtforms.mo +0 -0
  95. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/it/LC_MESSAGES/wtforms.po +0 -0
  96. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.mo +0 -0
  97. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.po +0 -0
  98. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.mo +0 -0
  99. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.po +0 -0
  100. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.mo +0 -0
  101. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.po +0 -0
  102. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.mo +0 -0
  103. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.po +0 -0
  104. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.mo +0 -0
  105. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.po +0 -0
  106. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.mo +0 -0
  107. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.po +0 -0
  108. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.mo +0 -0
  109. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.po +0 -0
  110. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.mo +0 -0
  111. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.po +0 -0
  112. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.mo +0 -0
  113. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.po +0 -0
  114. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.mo +0 -0
  115. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.po +0 -0
  116. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/sr/LC_MESSAGES/wtforms.mo +0 -0
  117. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/sr/LC_MESSAGES/wtforms.po +0 -0
  118. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.mo +0 -0
  119. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.po +0 -0
  120. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.mo +0 -0
  121. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.po +0 -0
  122. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.mo +0 -0
  123. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.po +0 -0
  124. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.mo +0 -0
  125. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.po +0 -0
  126. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/wtforms.pot +0 -0
  127. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.mo +0 -0
  128. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.po +0 -0
  129. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.mo +0 -0
  130. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.po +0 -0
  131. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/meta.py +0 -0
  132. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/utils.py +0 -0
  133. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/validators.py +0 -0
  134. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/widgets/__init__.py +0 -0
  135. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/src/wtforms/widgets/core.py +0 -0
  136. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/__init__.py +0 -0
  137. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/common.py +0 -0
  138. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/conftest.py +0 -0
  139. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/__init__.py +0 -0
  140. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_boolean.py +0 -0
  141. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_button.py +0 -0
  142. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_color.py +0 -0
  143. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_custom.py +0 -0
  144. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_defaults.py +0 -0
  145. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_field.py +0 -0
  146. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_file.py +0 -0
  147. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_filters.py +0 -0
  148. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_flags.py +0 -0
  149. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_hidden.py +0 -0
  150. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_html5.py +0 -0
  151. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_label.py +0 -0
  152. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_month.py +0 -0
  153. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_password.py +0 -0
  154. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_string.py +0 -0
  155. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_submit.py +0 -0
  156. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/fields/test_textarea.py +0 -0
  157. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/test_csrf.py +0 -0
  158. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/test_i18n.py +0 -0
  159. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/test_locale_babel.py +0 -0
  160. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/test_webob.py +0 -0
  161. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/test_widgets.py +0 -0
  162. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/__init__.py +0 -0
  163. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_anyof.py +0 -0
  164. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_data_required.py +0 -0
  165. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_date_range.py +0 -0
  166. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_disabled.py +0 -0
  167. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_email.py +0 -0
  168. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_equal_to.py +0 -0
  169. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_input_required.py +0 -0
  170. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_ipaddress.py +0 -0
  171. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_lazy_proxy.py +0 -0
  172. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_length.py +0 -0
  173. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_mac_address.py +0 -0
  174. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_noneof.py +0 -0
  175. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_number_range.py +0 -0
  176. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_optional.py +0 -0
  177. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_readonly.py +0 -0
  178. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_regexp.py +0 -0
  179. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_url.py +0 -0
  180. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_uuid.py +0 -0
  181. {wtforms-3.3.0b2 → wtforms-3.3.0b4}/tests/validators/test_validators.py +0 -0
@@ -1,5 +1,44 @@
1
1
  .. currentmodule:: wtforms
2
2
 
3
+ Version 3.3.0b4
4
+ ---------------
5
+
6
+ Released 2026-10-23
7
+
8
+ - Drop support for Python 3.10. Add support for Python 3.15.
9
+ - :func:`fields.enum_choices` and :func:`~wtforms.datalist.enum_datalist`
10
+ label :class:`enum.IntEnum` members with their name on PyPy too, instead of
11
+ their value.
12
+ - :class:`~datalist.DataList` ``choices`` accepts the shorthand
13
+ ``{value: label}`` dict syntax. :issue:`886`
14
+ - Default ``invalid_value_message`` and ``invalid_choice_message`` are
15
+ translated when the error is raised, not when the field is built. Forms can
16
+ be built again outside of a translation context.
17
+ - ``choices`` callables on :class:`~fields.SelectField` and
18
+ :class:`~datalist.DataList` run on the first read and their result is
19
+ cached. Calling :meth:`~form.BaseForm.post_process` again refreshes them.
20
+ This also applies to entries added with
21
+ :meth:`~fields.FieldList.append_entry` and
22
+ :meth:`~fields.FieldList.insert_entry`.
23
+ - Passing a callable to ``SelectFieldBase._choices_from_input`` is
24
+ deprecated.
25
+ - Numeric, date, time and select fields report a validation error instead of
26
+ raising a :class:`TypeError` when the input is not a string.
27
+ - The labels of :class:`~fields.FieldList` entries point to the right field
28
+ after :meth:`~fields.FieldList.insert_entry` or
29
+ :meth:`~fields.FieldList.pop_entry` renumbers the entries.
30
+
31
+ Version 3.3.0b3
32
+ ---------------
33
+
34
+ Released 2026-06-12
35
+
36
+ - Add :func:`fields.enum_choices`, :func:`fields.enum_coerce` and
37
+ :func:`~wtforms.datalist.enum_datalist` for Enum-backed choices,
38
+ replacing ``SelectChoice.from_enum`` and friends. :issue:`922`
39
+ - An Enum ``coerce`` is no longer wrapped, and options default to
40
+ ``member.value``. :issue:`922`
41
+
3
42
  Version 3.3.0b2
4
43
  ---------------
5
44
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: WTForms
3
- Version: 3.3.0b2
3
+ Version: 3.3.0b4
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,17 +330,15 @@ 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
337
338
  option cannot be applied to your problem you may wish to skip choice
338
339
  validation (see below).
339
340
 
340
- **Select fields with ``<optgroup>``**::
341
-
342
- Use :class:`SelectChoice` to assign an option to an ``<optgroup>``.
341
+ **Select fields with optgroup**::
343
342
 
344
343
  class PastebinEntry(Form):
345
344
  language = SelectField('Programming Language', choices=[
@@ -349,6 +348,8 @@ Choice Fields
349
348
  SelectChoice('text', 'Plain Text'),
350
349
  ])
351
350
 
351
+ Use :class:`SelectChoice` to assign an option to an ``<optgroup>``.
352
+
352
353
  **Select fields with dynamic choice values**::
353
354
 
354
355
  def available_groups():
@@ -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]
@@ -400,28 +406,44 @@ Choice Fields
400
406
  **Select fields backed by an Enum**::
401
407
 
402
408
  from enum import Enum
409
+ from wtforms.fields import enum_choices, enum_coerce
403
410
 
404
411
  class Color(Enum):
405
- RED = 1
406
- GREEN = 2
407
- BLUE = 3
412
+ RED = "red"
413
+ GREEN = "green"
414
+ BLUE = "blue"
408
415
 
409
416
  class PaintForm(Form):
410
- color = SelectField(choices=SelectChoice.from_enum(Color), coerce=Color)
417
+ color = SelectField(
418
+ choices=enum_choices(Color),
419
+ coerce=enum_coerce(Color),
420
+ )
421
+
422
+ :func:`~wtforms.fields.enum_choices` builds the option list from
423
+ the Enum members; by default the HTML ``value`` of each option is the
424
+ member's ``value`` (``"red"``, ``"green"``, ...).
425
+ :func:`~wtforms.fields.enum_coerce` returns the matching ``coerce``
426
+ callable that resolves the submitted string back into a member, so
427
+ ``form.color.data`` is a ``Color`` member after submit. Pre-selecting
428
+ works the usual way, with an Enum member: ``PaintForm(color=Color.RED)``.
411
429
 
412
- :meth:`SelectChoice.from_enum` builds the option list from the Enum items;
413
- the HTML ``value`` of each option is the item's ``name``. Passing the
414
- Enum class itself as ``coerce`` installs the matching coercion, so
415
- ``form.color.data`` is a ``Color`` item after submit. Pre-selecting
416
- works the usual way, with an Enum item: ``PaintForm(color=Color.RED)``.
430
+ Use ``by="name"`` on **both** helpers when the Enum value is not a good
431
+ transport identifier — non-unique, non-serialisable, or when you simply
432
+ want ``member.name`` on the wire::
417
433
 
418
- By default the option label is ``str(item)`` if the Enum defines its
434
+ class PaintForm(Form):
435
+ color = SelectField(
436
+ choices=enum_choices(Color, by="name"),
437
+ coerce=enum_coerce(Color, by="name"),
438
+ )
439
+
440
+ By default the option label is ``str(member)`` if the Enum defines its
419
441
  own ``__str__`` (also the case for :class:`enum.StrEnum`), otherwise
420
- ``item.name``. To customise, pass a ``label`` callable taking an Enum
421
- item and returning the label string::
442
+ ``member.name``. To customise, pass a ``label`` callable taking an Enum
443
+ member and returning the label string::
422
444
 
423
- SelectChoice.from_enum(Color, label=lambda item: item.name.title())
424
- # → [SelectChoice('RED', 'Red'), SelectChoice('GREEN', 'Green'), SelectChoice('BLUE', 'Blue')]
445
+ enum_choices(Color, label=lambda member: member.name.title())
446
+ # → [SelectChoice('red', 'Red'), SelectChoice('green', 'Green'), SelectChoice('blue', 'Blue')]
425
447
 
426
448
  **Skipping choice validation**::
427
449
 
@@ -454,6 +476,10 @@ Choice Fields
454
476
  which are not in the given choices list will cause validation on the field
455
477
  to fail.
456
478
 
479
+ .. autofunction:: wtforms.fields.enum_choices
480
+
481
+ .. autofunction:: wtforms.fields.enum_coerce
482
+
457
483
  Submit fields
458
484
  -------------
459
485
 
@@ -580,8 +606,6 @@ Data Lists
580
606
 
581
607
  .. currentmodule:: wtforms
582
608
 
583
- .. autoclass:: DataListChoice
584
-
585
609
  .. class:: DataList(choices=None, *, render_kw=None, widget=None)
586
610
 
587
611
  A :mdn-tag:`datalist` of suggestions. Unlike
@@ -592,12 +616,15 @@ Data Lists
592
616
 
593
617
  ``choices`` is either a list of :class:`~wtforms.DataListChoice`
594
618
  (or plain strings, in which case the string is used as both value
595
- and label), or a callable invoked at render time. The callable
596
- may take no argument (``fn()``) for a static list, or ``(field)``
597
- to adapt the suggestions to the current field value at each
598
- 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::
599
626
 
600
- DataList(lambda field: search(field.data))
627
+ DataList(lambda form, field: search(field.data))
601
628
 
602
629
  **Inline DataList owned by a field**
603
630
 
@@ -624,7 +651,7 @@ Data Lists
624
651
  class F(Form):
625
652
  items = FieldList(
626
653
  StringField(
627
- datalist=DataList(lambda field: suggest(field.data))
654
+ datalist=DataList(lambda form, field: suggest(field.data))
628
655
  ),
629
656
  min_entries=3,
630
657
  )
@@ -658,6 +685,9 @@ Data Lists
658
685
  :class:`~wtforms.fields.TextAreaField` is silently ignored by the
659
686
  browser.
660
687
 
688
+ .. autoclass:: DataListChoice
689
+
690
+ .. autofunction:: wtforms.datalist.enum_datalist
661
691
 
662
692
  .. currentmodule:: wtforms.fields
663
693
 
@@ -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__
@@ -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 = [
@@ -2,6 +2,7 @@ from wtforms import validators
2
2
  from wtforms import widgets
3
3
  from wtforms.datalist import DataList
4
4
  from wtforms.datalist import DataListChoice
5
+ from wtforms.datalist import enum_datalist
5
6
  from wtforms.fields.choices import Choice
6
7
  from wtforms.fields.choices import RadioField
7
8
  from wtforms.fields.choices import SelectChoice
@@ -41,7 +42,7 @@ from wtforms.fields.simple import URLField
41
42
  from wtforms.form import Form
42
43
  from wtforms.validators import ValidationError
43
44
 
44
- __version__ = "3.3.0b2"
45
+ __version__ = "3.3.0b4"
45
46
 
46
47
  __all__ = [
47
48
  "validators",
@@ -86,4 +87,5 @@ __all__ = [
86
87
  "Choice",
87
88
  "SelectChoice",
88
89
  "DataListChoice",
90
+ "enum_datalist",
89
91
  ]
@@ -3,10 +3,11 @@ from dataclasses import dataclass
3
3
  from dataclasses import field
4
4
 
5
5
  from wtforms import widgets
6
- from wtforms._compat import get_signature
6
+ from wtforms.fields.choices import _enum_options
7
+ from wtforms.fields.choices import _invoke_choices_callable
7
8
  from wtforms.fields.choices import Choice
8
9
 
9
- __all__ = ("DataList", "DataListChoice")
10
+ __all__ = ("DataList", "DataListChoice", "enum_datalist")
10
11
 
11
12
 
12
13
  @dataclass
@@ -35,16 +36,6 @@ class DataListChoice:
35
36
  def __iter__(self):
36
37
  return iter((self.value, self.label, self.render_kw))
37
38
 
38
- @classmethod
39
- def from_enum(cls, enum_cls, *, label=None):
40
- """Build a list of choices from an :class:`enum.Enum` class.
41
-
42
- See :meth:`SelectChoice.from_enum` for details.
43
- """
44
- if label is None:
45
- label = str if "__str__" in enum_cls.__dict__ else lambda m: m.name
46
- return [cls(value=m.name, label=label(m)) for m in enum_cls]
47
-
48
39
  @classmethod
49
40
  def from_input(cls, input):
50
41
  """Coerce a value passed by the user into a :class:`DataListChoice`."""
@@ -73,6 +64,21 @@ class DataListChoice:
73
64
  return cls(*input)
74
65
 
75
66
 
67
+ def enum_datalist(enum_cls, *, by="value", label=None):
68
+ """Build a list of :class:`DataListChoice` from an :class:`enum.Enum` class.
69
+
70
+ Same semantics as :func:`~wtforms.fields.enum_choices`: ``by`` selects
71
+ which member attribute becomes the ``<option>`` value (``"value"`` by
72
+ default, ``"name"`` otherwise) and ``label`` defaults to ``str(member)``
73
+ when the Enum defines its own ``__str__`` or is a :class:`enum.StrEnum`,
74
+ else ``member.name``.
75
+
76
+ A ``<datalist>`` only suggests values for a free-text field, so there is
77
+ no coercion counterpart — the field stores whatever string is submitted.
78
+ """
79
+ return _enum_options(enum_cls, by, label, DataListChoice)
80
+
81
+
76
82
  class DataList:
77
83
  """A ``<datalist>`` of suggestions attached to a single field.
78
84
 
@@ -86,6 +92,7 @@ class DataList:
86
92
  def __init__(self, choices=None, *, render_kw=None, widget=None):
87
93
  self._raw_choices = choices
88
94
  self._choices = None if callable(choices) else choices
95
+ self._field = None
89
96
  self.render_kw = render_kw or {}
90
97
  if widget is not None:
91
98
  self.widget = widget
@@ -95,27 +102,30 @@ class DataList:
95
102
  clone = DataList.__new__(DataList)
96
103
  clone._raw_choices = self._raw_choices
97
104
  clone._choices = None if callable(self._raw_choices) else self._raw_choices
105
+ clone._field = None
98
106
  clone.render_kw = self.render_kw
99
107
  clone.widget = self.widget
100
108
  clone.id = id
101
109
  return clone
102
110
 
103
- def _resolve(self, field):
104
- raw = self._raw_choices
105
- if not callable(raw):
106
- return
107
- try:
108
- sig = get_signature(raw)
109
- sig.bind(field._form, field)
110
- except TypeError:
111
- self._choices = raw()
112
- return
113
- self._choices = raw(field._form, field)
111
+ def _invalidate(self, field):
112
+ """Mark the choices for a lazy refresh on the next :meth:`iter_choices`."""
113
+ if callable(self._raw_choices):
114
+ self._field = field
114
115
 
115
116
  def iter_choices(self, field=None):
117
+ if (bound := self._field) is not None:
118
+ self._field = None
119
+ self._choices = _invoke_choices_callable(
120
+ self._raw_choices, bound._form, bound
121
+ )
116
122
  raw = self._choices
117
123
  if raw is None:
118
124
  return []
125
+ if isinstance(raw, dict):
126
+ return [
127
+ DataListChoice(value=value, label=label) for value, label in raw.items()
128
+ ]
119
129
  return [DataListChoice.from_input(item) for item in raw]
120
130
 
121
131
  def __call__(self, field=None, **kwargs):
@@ -1,4 +1,6 @@
1
1
  from wtforms.fields.choices import Choice
2
+ from wtforms.fields.choices import enum_choices
3
+ from wtforms.fields.choices import enum_coerce
2
4
  from wtforms.fields.choices import RadioField
3
5
  from wtforms.fields.choices import SelectChoice
4
6
  from wtforms.fields.choices import SelectField
@@ -46,6 +48,8 @@ __all__ = [
46
48
  "SelectMultipleField",
47
49
  "SelectFieldBase",
48
50
  "RadioField",
51
+ "enum_choices",
52
+ "enum_coerce",
49
53
  "DateTimeField",
50
54
  "DateField",
51
55
  "TimeField",