Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wtforms/datalist.py: 39%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

66 statements  

1import warnings 

2from dataclasses import dataclass 

3from dataclasses import field 

4from dataclasses import replace 

5 

6from wtforms import widgets 

7from wtforms.fields.choices import _enum_options 

8from wtforms.fields.choices import _invoke_choices_callable 

9from wtforms.fields.choices import Choice 

10 

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

12 

13 

14@dataclass 

15class DataListChoice: 

16 """ 

17 An option declared via :class:`~wtforms.DataList`'s ``choices=`` 

18 parameter. 

19 

20 :param value: 

21 The value rendered as the ``<option>``'s ``value`` attribute. 

22 :param label: 

23 The label of the option. Defaults to ``value`` when omitted. 

24 :param render_kw: 

25 A dict containing HTML attributes that will be rendered 

26 with the option. Defaults to an empty dict when omitted. 

27 """ 

28 

29 value: str 

30 label: str | None = None 

31 render_kw: dict = field(default_factory=dict) 

32 

33 def __post_init__(self): 

34 if self.label is None: 

35 self.label = self.value 

36 

37 def __iter__(self): 

38 return iter((self.value, self.label, self.render_kw)) 

39 

40 @classmethod 

41 def from_input(cls, input): 

42 """Coerce a value passed by the user into a :class:`DataListChoice`.""" 

43 if isinstance(input, DataListChoice): 

44 return input 

45 

46 if isinstance(input, Choice): 

47 warnings.warn( 

48 "Passing Choice to a DataList is deprecated; Choice is the " 

49 "output type returned by iter_choices(). Use DataListChoice " 

50 "instead. Support for Choice as input will be removed in " 

51 "WTForms 4.0.", 

52 DeprecationWarning, 

53 stacklevel=4, 

54 ) 

55 return cls( 

56 value=input.value, 

57 label=input.label, 

58 render_kw=input.render_kw, 

59 ) 

60 

61 if isinstance(input, str): 

62 return cls(value=input) 

63 

64 if isinstance(input, tuple): 

65 return cls(*input) 

66 

67 

68def enum_datalist(enum_cls, *, by="value", label=None): 

69 """Build a list of :class:`DataListChoice` from an :class:`enum.Enum` class. 

70 

71 Same semantics as :func:`~wtforms.fields.enum_choices`: ``by`` selects 

72 which member attribute becomes the ``<option>`` value (``"value"`` by 

73 default, ``"name"`` otherwise) and ``label`` defaults to ``str(member)`` 

74 when the Enum defines its own ``__str__`` or is a :class:`enum.StrEnum`, 

75 else ``member.name``. 

76 

77 A ``<datalist>`` only suggests values for a free-text field, so there is 

78 no coercion counterpart — the field stores whatever string is submitted. 

79 """ 

80 return _enum_options(enum_cls, by, label, DataListChoice) 

81 

82 

83class DataList: 

84 """A ``<datalist>`` of suggestions attached to a single field. 

85 

86 Passed to a :class:`~wtforms.Field` via its ``datalist=`` parameter 

87 to add an autocomplete-style list of choices. See the WTForms 

88 fields documentation for usage. 

89 """ 

90 

91 widget = widgets.DataListWidget() 

92 

93 def __init__(self, choices=None, *, render_kw=None, widget=None): 

94 self._raw_choices = choices 

95 self._choices = None if callable(choices) else choices 

96 self._field = None 

97 self.render_kw = render_kw or {} 

98 if widget is not None: 

99 self.widget = widget 

100 self.id = None 

101 

102 def _clone(self, id): 

103 clone = DataList.__new__(DataList) 

104 clone._raw_choices = self._raw_choices 

105 clone._choices = None if callable(self._raw_choices) else self._raw_choices 

106 clone._field = None 

107 clone.render_kw = dict(self.render_kw) 

108 clone.widget = self.widget 

109 clone.id = id 

110 return clone 

111 

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 

116 

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 ) 

123 raw = self._choices 

124 if raw is None: 

125 return [] 

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 ] 

134 

135 def __call__(self, field=None, **kwargs): 

136 return self.widget(self, field=field, **kwargs)