wtforms-htmx 0.1.0__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.
@@ -0,0 +1,28 @@
1
+ Copyright 2026 WTForms
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are
5
+ met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+
14
+ 3. Neither the name of the copyright holder nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.4
2
+ Name: wtforms-htmx
3
+ Version: 0.1.0
4
+ Summary: Interactive WTForms forms declared in Python and powered by htmx, with no JavaScript to write.
5
+ Keywords: wtforms,htmx,forms,validation
6
+ License-Expression: BSD-3-Clause
7
+ License-File: LICENSE.rst
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Web Environment
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Programming Language :: Python :: 3.15
19
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
20
+ Requires-Dist: wtforms>=3.3.0,<4
21
+ Maintainer: Éloi Rivard
22
+ Maintainer-email: Éloi Rivard <eloi@yaal.coop>
23
+ Requires-Python: >=3.11
24
+ Project-URL: Documentation, https://wtforms-htmx.readthedocs.io/
25
+ Project-URL: Changes, https://wtforms-htmx.readthedocs.io/changes/
26
+ Project-URL: Source Code, https://github.com/yaal-coop/wtforms-htmx
27
+ Project-URL: Issue Tracker, https://github.com/yaal-coop/wtforms-htmx/issues
28
+ Description-Content-Type: text/markdown
29
+
30
+ <p align="center">
31
+ <picture>
32
+ <source media="(prefers-color-scheme: dark)" srcset="docs/_static/logo-dark.webp">
33
+ <img src="docs/_static/logo.webp" alt="wtforms-htmx">
34
+ </picture>
35
+ </p>
36
+
37
+ # wtforms-htmx
38
+
39
+ Interactive [WTForms](https://wtforms.readthedocs.io/) forms declared in
40
+ Python and powered by [htmx](https://htmx.org/), with no JavaScript to
41
+ write, or even any htmx.
42
+
43
+ <p align="center">
44
+ <picture>
45
+ <source media="(prefers-color-scheme: dark)" srcset="https://wtforms-htmx.readthedocs.io/en/latest/_static/screencasts/title-slug-dark.webp">
46
+ <img src="https://wtforms-htmx.readthedocs.io/en/latest/_static/screencasts/title-slug-light.webp" width="700" alt="Typing a title fills in its slug as you type.">
47
+ </picture>
48
+ </p>
49
+
50
+ Here's (almost) everything wtforms-htmx adds on top of a regular form:
51
+
52
+ ```python
53
+ class PostForm(HtmxForm):
54
+ title = StringField().triggers("slug", slugify)
55
+ slug = StringField()
56
+ ```
57
+
58
+ ## Features
59
+
60
+ - **Declare interactivity in Python.**
61
+ Right on the field declaration, not scattered across `hx-*`
62
+ attributes.
63
+ - **No extra endpoints.** Interactions are handled by the form's
64
+ existing view. You don't write a route for each one.
65
+ - **Progressive enhancement.** The form works without JavaScript;
66
+ htmx layers live updates and partial swaps on top. Same form, two
67
+ render paths.
68
+ - **Opinionated defaults, fully overridable.** wtforms-htmx renders
69
+ the right `hx-*` for you, but they're yours to override.
70
+ - **Common patterns in a few lines.** Inline validation and `FieldList`
71
+ add/remove/reorder are built in. The examples show how to build
72
+ autocomplete, comboboxes and dependent fields.
73
+
74
+ Full documentation: <https://wtforms-htmx.readthedocs.io/>
75
+
76
+ ## Example app
77
+
78
+ A runnable gallery of examples lives in `examples/`. The
79
+ [documentation](https://wtforms-htmx.readthedocs.io/) shows most of them with a
80
+ screencast. To run it locally:
81
+
82
+ ```sh
83
+ uv sync --group example
84
+ uv run python -m examples
85
+ ```
86
+
87
+ Then open <http://127.0.0.1:5000/>.
@@ -0,0 +1,58 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="docs/_static/logo-dark.webp">
4
+ <img src="docs/_static/logo.webp" alt="wtforms-htmx">
5
+ </picture>
6
+ </p>
7
+
8
+ # wtforms-htmx
9
+
10
+ Interactive [WTForms](https://wtforms.readthedocs.io/) forms declared in
11
+ Python and powered by [htmx](https://htmx.org/), with no JavaScript to
12
+ write, or even any htmx.
13
+
14
+ <p align="center">
15
+ <picture>
16
+ <source media="(prefers-color-scheme: dark)" srcset="https://wtforms-htmx.readthedocs.io/en/latest/_static/screencasts/title-slug-dark.webp">
17
+ <img src="https://wtforms-htmx.readthedocs.io/en/latest/_static/screencasts/title-slug-light.webp" width="700" alt="Typing a title fills in its slug as you type.">
18
+ </picture>
19
+ </p>
20
+
21
+ Here's (almost) everything wtforms-htmx adds on top of a regular form:
22
+
23
+ ```python
24
+ class PostForm(HtmxForm):
25
+ title = StringField().triggers("slug", slugify)
26
+ slug = StringField()
27
+ ```
28
+
29
+ ## Features
30
+
31
+ - **Declare interactivity in Python.**
32
+ Right on the field declaration, not scattered across `hx-*`
33
+ attributes.
34
+ - **No extra endpoints.** Interactions are handled by the form's
35
+ existing view. You don't write a route for each one.
36
+ - **Progressive enhancement.** The form works without JavaScript;
37
+ htmx layers live updates and partial swaps on top. Same form, two
38
+ render paths.
39
+ - **Opinionated defaults, fully overridable.** wtforms-htmx renders
40
+ the right `hx-*` for you, but they're yours to override.
41
+ - **Common patterns in a few lines.** Inline validation and `FieldList`
42
+ add/remove/reorder are built in. The examples show how to build
43
+ autocomplete, comboboxes and dependent fields.
44
+
45
+ Full documentation: <https://wtforms-htmx.readthedocs.io/>
46
+
47
+ ## Example app
48
+
49
+ A runnable gallery of examples lives in `examples/`. The
50
+ [documentation](https://wtforms-htmx.readthedocs.io/) shows most of them with a
51
+ screencast. To run it locally:
52
+
53
+ ```sh
54
+ uv sync --group example
55
+ uv run python -m examples
56
+ ```
57
+
58
+ Then open <http://127.0.0.1:5000/>.
@@ -0,0 +1,298 @@
1
+ [project]
2
+ name = "wtforms-htmx"
3
+ version = "0.1.0"
4
+ description = "Interactive WTForms forms declared in Python and powered by htmx, with no JavaScript to write."
5
+ readme = "README.md"
6
+ license = "BSD-3-Clause"
7
+ license-files = ["LICENSE.rst"]
8
+ keywords = [
9
+ "wtforms",
10
+ "htmx",
11
+ "forms",
12
+ "validation",
13
+ ]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Web Environment",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Programming Language :: Python :: 3.15",
26
+ "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
27
+ ]
28
+ requires-python = ">=3.11"
29
+ dependencies = ["wtforms>=3.3.0,<4"]
30
+
31
+ [[project.maintainers]]
32
+ name = "Éloi Rivard"
33
+ email = "eloi@yaal.coop"
34
+
35
+ [project.urls]
36
+ Documentation = "https://wtforms-htmx.readthedocs.io/"
37
+ Changes = "https://wtforms-htmx.readthedocs.io/changes/"
38
+ "Source Code" = "https://github.com/yaal-coop/wtforms-htmx"
39
+ "Issue Tracker" = "https://github.com/yaal-coop/wtforms-htmx/issues"
40
+
41
+ [build-system]
42
+ requires = ["uv_build>=0.13.1,<0.14"]
43
+ build-backend = "uv_build"
44
+
45
+ [tool.uv]
46
+ required-version = ">=0.13.1"
47
+
48
+ [tool.pytest.ini_options]
49
+ testpaths = ["tests/unit"]
50
+ pythonpath = ["."]
51
+ filterwarnings = ["error"]
52
+
53
+ [tool.coverage.run]
54
+ branch = true
55
+ source = [
56
+ "wtforms_htmx",
57
+ "tests",
58
+ ]
59
+
60
+ [tool.coverage.paths]
61
+ source = [
62
+ "src",
63
+ "*/site-packages",
64
+ ]
65
+
66
+ [tool.coverage.report]
67
+ fail_under = 100
68
+ exclude_lines = [
69
+ "pragma: no cover",
70
+ "except ImportError",
71
+ "if TYPE_CHECKING:",
72
+ ]
73
+
74
+ [tool.ruff]
75
+ src = ["src"]
76
+ fix = true
77
+ show-fixes = true
78
+ output-format = "full"
79
+
80
+ [tool.ruff.lint]
81
+ select = [
82
+ "B",
83
+ "C90",
84
+ "D",
85
+ "E",
86
+ "F",
87
+ "I",
88
+ "UP",
89
+ "W",
90
+ "SIM",
91
+ "RET",
92
+ "BLE",
93
+ "TRY",
94
+ "ERA",
95
+ "PIE",
96
+ "RUF",
97
+ "DTZ",
98
+ "PTH",
99
+ ]
100
+ ignore = [
101
+ "E501",
102
+ "E722",
103
+ "D100",
104
+ "D101",
105
+ "D102",
106
+ "D103",
107
+ "D104",
108
+ "D105",
109
+ "D106",
110
+ "D107",
111
+ "D203",
112
+ "D213",
113
+ "RUF001",
114
+ "RUF002",
115
+ "RUF003",
116
+ "TRY003",
117
+ ]
118
+
119
+ [tool.ruff.lint.per-file-ignores]
120
+ "examples/**" = [
121
+ "D205",
122
+ "D400",
123
+ "D404",
124
+ "D415",
125
+ ]
126
+
127
+ [tool.ruff.lint.isort]
128
+ force-single-line = true
129
+ order-by-type = false
130
+
131
+ [tool.ruff.lint.mccabe]
132
+ max-complexity = 10
133
+
134
+ [tool.mypy]
135
+ python_version = "3.11"
136
+ packages = ["wtforms_htmx"]
137
+ mypy_path = "src"
138
+ ignore_missing_imports = true
139
+
140
+ [tool.tox]
141
+ requires = [
142
+ "tox>=4.22",
143
+ "tox-uv>=1.16",
144
+ ]
145
+ env_list = [
146
+ "py3.11",
147
+ "py3.12",
148
+ "py3.13",
149
+ "py3.14",
150
+ "py3.15",
151
+ "minversions",
152
+ "typing",
153
+ ]
154
+
155
+ [tool.tox.env_run_base]
156
+ runner = "uv-venv-lock-runner"
157
+ dependency_groups = ["test"]
158
+ commands = [[
159
+ "pytest",
160
+ { replace = "posargs", extend = true },
161
+ ]]
162
+
163
+ [tool.tox.env.typing]
164
+ runner = "uv-venv-lock-runner"
165
+ dependency_groups = ["dev"]
166
+ commands = [["mypy"]]
167
+
168
+ [tool.tox.env.coverage]
169
+ runner = "uv-venv-lock-runner"
170
+ allowlist_externals = ["npm"]
171
+ commands_pre = [[
172
+ "npm",
173
+ "ci",
174
+ "--no-audit",
175
+ "--no-fund",
176
+ ]]
177
+ dependency_groups = [
178
+ "e2e",
179
+ "test",
180
+ ]
181
+ commands = [
182
+ [
183
+ "coverage",
184
+ "erase",
185
+ ],
186
+ [
187
+ "coverage",
188
+ "run",
189
+ "--module",
190
+ "pytest",
191
+ "tests/unit",
192
+ "tests/e2e",
193
+ "--browser",
194
+ "chromium",
195
+ { replace = "posargs", extend = true },
196
+ ],
197
+ [
198
+ "coverage",
199
+ "html",
200
+ "--fail-under",
201
+ "0",
202
+ ],
203
+ [
204
+ "coverage",
205
+ "report",
206
+ "--fail-under",
207
+ "0",
208
+ ],
209
+ ]
210
+
211
+ [tool.tox.env.minversions]
212
+ runner = "uv-venv-runner"
213
+ base_python = ["py3.11"]
214
+ uv_resolution = "lowest-direct"
215
+ dependency_groups = ["test"]
216
+ commands = [[
217
+ "pytest",
218
+ "tests/unit",
219
+ { replace = "posargs", extend = true },
220
+ ]]
221
+
222
+ [tool.tox.env.e2e]
223
+ runner = "uv-venv-lock-runner"
224
+ allowlist_externals = ["npm"]
225
+ commands_pre = [[
226
+ "npm",
227
+ "ci",
228
+ "--no-audit",
229
+ "--no-fund",
230
+ ]]
231
+ dependency_groups = ["e2e"]
232
+ commands = [[
233
+ "pytest",
234
+ "tests/e2e",
235
+ "--browser",
236
+ "chromium",
237
+ "--browser",
238
+ "firefox",
239
+ { replace = "posargs", extend = true },
240
+ ]]
241
+
242
+ [tool.tox.env.e2e-htmx2]
243
+ runner = "uv-venv-lock-runner"
244
+ allowlist_externals = ["npm"]
245
+ commands_pre = [[
246
+ "npm",
247
+ "ci",
248
+ "--no-audit",
249
+ "--no-fund",
250
+ ]]
251
+ dependency_groups = ["e2e"]
252
+ commands = [[
253
+ "pytest",
254
+ "tests/e2e",
255
+ "--browser",
256
+ "chromium",
257
+ "--htmx-version",
258
+ "2",
259
+ "--ignore-glob",
260
+ "tests/e2e/*_nojs.py",
261
+ { replace = "posargs", extend = true },
262
+ ]]
263
+
264
+ [dependency-groups]
265
+ doc = [
266
+ "pallets-sphinx-themes>=2.5.0",
267
+ "sphinx>=8.1.0",
268
+ "sphinx-issues>=6.0.0",
269
+ "sphinxcontrib-mermaid>=2.1.1",
270
+ "sphinxcontrib-screenshot>=1.2.0; python_version < '3.12'",
271
+ "sphinxcontrib-screenshot>=2.1.2; python_version >= '3.12'",
272
+ ]
273
+ test = [
274
+ "pytest>=8.0",
275
+ "pytest-cov>=5.0",
276
+ "flask>=3.0",
277
+ ]
278
+ dev = [
279
+ { include-group = "test" },
280
+ { include-group = "example" },
281
+ "mypy>=1.11",
282
+ "prek>=0.3.9",
283
+ "pytest-xdist>=3.8.0",
284
+ "tox-uv>=1.35.1",
285
+ ]
286
+ example = [
287
+ "flask>=3.0",
288
+ "email-validator>=2",
289
+ "mistune>=3",
290
+ "pgeocode>=0.5.0",
291
+ "pycountry>=24",
292
+ ]
293
+ e2e = [
294
+ { include-group = "example" },
295
+ "pytest>=8.0",
296
+ "pytest-playwright>=0.5",
297
+ "pytest-xdist>=3.8.0",
298
+ ]
@@ -0,0 +1,214 @@
1
+ [project]
2
+ name = "wtforms-htmx"
3
+ version = "0.1.0"
4
+ description = "Interactive WTForms forms declared in Python and powered by htmx, with no JavaScript to write."
5
+ readme = "README.md"
6
+ license = "BSD-3-Clause"
7
+ license-files = ["LICENSE.rst"]
8
+ maintainers = [
9
+ { name = "Éloi Rivard", email = "eloi@yaal.coop" },
10
+ ]
11
+ keywords = ["wtforms", "htmx", "forms", "validation"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Environment :: Web Environment",
15
+ "Intended Audience :: Developers",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Programming Language :: Python :: 3.15",
24
+ "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
25
+ ]
26
+ requires-python = ">=3.11"
27
+ dependencies = [
28
+ "wtforms>=3.3.0,<4",
29
+ ]
30
+
31
+ [project.urls]
32
+ Documentation = "https://wtforms-htmx.readthedocs.io/"
33
+ Changes = "https://wtforms-htmx.readthedocs.io/changes/"
34
+ "Source Code" = "https://github.com/yaal-coop/wtforms-htmx"
35
+ "Issue Tracker" = "https://github.com/yaal-coop/wtforms-htmx/issues"
36
+
37
+ [build-system]
38
+ requires = ["uv_build>=0.13.1,<0.14"]
39
+ build-backend = "uv_build"
40
+
41
+ [tool.uv]
42
+ required-version = ">=0.13.1"
43
+
44
+ [dependency-groups]
45
+ doc = [
46
+ "pallets-sphinx-themes>=2.5.0",
47
+ "sphinx>=8.1.0",
48
+ "sphinx-issues>=6.0.0",
49
+ "sphinxcontrib-mermaid>=2.1.1",
50
+ "sphinxcontrib-screenshot>=1.2.0; python_version < '3.12'",
51
+ "sphinxcontrib-screenshot>=2.1.2; python_version >= '3.12'",
52
+ ]
53
+ test = [
54
+ "pytest>=8.0",
55
+ "pytest-cov>=5.0",
56
+ "flask>=3.0",
57
+ ]
58
+ dev = [
59
+ { include-group = "test" },
60
+ { include-group = "example" },
61
+ "mypy>=1.11",
62
+ "prek>=0.3.9",
63
+ "pytest-xdist>=3.8.0",
64
+ "tox-uv>=1.35.1",
65
+ ]
66
+ example = [
67
+ "flask>=3.0",
68
+ "email-validator>=2",
69
+ "mistune>=3",
70
+ "pgeocode>=0.5.0",
71
+ "pycountry>=24",
72
+ ]
73
+ e2e = [
74
+ { include-group = "example" },
75
+ "pytest>=8.0",
76
+ "pytest-playwright>=0.5",
77
+ "pytest-xdist>=3.8.0",
78
+ ]
79
+
80
+ # tests/e2e needs pytest-playwright and is run explicitly (tox -e e2e, the e2e
81
+ # CI job); the default run stays browser-free so the unit suite needs no Playwright.
82
+ [tool.pytest.ini_options]
83
+ testpaths = ["tests/unit"]
84
+ pythonpath = ["."]
85
+ filterwarnings = ["error"]
86
+
87
+ [tool.coverage.run]
88
+ branch = true
89
+ source = ["wtforms_htmx", "tests"]
90
+
91
+ [tool.coverage.paths]
92
+ source = ["src", "*/site-packages"]
93
+
94
+ [tool.coverage.report]
95
+ fail_under = 100
96
+ exclude_lines = [
97
+ "pragma: no cover",
98
+ "except ImportError",
99
+ "if TYPE_CHECKING:",
100
+ ]
101
+
102
+ [tool.ruff]
103
+ src = ["src"]
104
+ fix = true
105
+ show-fixes = true
106
+ output-format = "full"
107
+
108
+ [tool.ruff.lint]
109
+ select = [
110
+ "B", # flake8-bugbear
111
+ "C90", # mccabe
112
+ "D", # pydocstyle
113
+ "E", # pycodestyle error
114
+ "F", # pyflakes
115
+ "I", # isort
116
+ "UP", # pyupgrade
117
+ "W", # pycodestyle warning
118
+ "SIM",
119
+ "RET",
120
+ "BLE",
121
+ "TRY",
122
+ "ERA",
123
+ "PIE",
124
+ "RUF",
125
+ "DTZ",
126
+ "PTH",
127
+ ]
128
+
129
+ ignore = [
130
+ "E501", # line-too-long
131
+ "E722", # bare-except
132
+ "D100", # public module
133
+ "D101", # public class
134
+ "D102", # public method
135
+ "D103", # public function
136
+ "D104", # public package
137
+ "D105", # magic method
138
+ "D106", # nested class
139
+ "D107", # public init
140
+ "D203", # no-blank-line-before-class
141
+ "D213", # multi-line-summary-second-line
142
+ "RUF001",
143
+ "RUF002",
144
+ "RUF003",
145
+ "TRY003", # raise-vanilla-args — dedicated exception classes would be overkill here
146
+ ]
147
+
148
+ [tool.ruff.lint.per-file-ignores]
149
+ "examples/**" = [
150
+ "D205", # blank-line-after-summary
151
+ "D400", # first-line-period
152
+ "D404", # docstring-starts-with-this
153
+ "D415", # first-line-punctuation
154
+ ]
155
+
156
+ [tool.ruff.lint.isort]
157
+ force-single-line = true
158
+ order-by-type = false
159
+
160
+ [tool.ruff.lint.mccabe]
161
+ max-complexity = 10
162
+
163
+ [tool.mypy]
164
+ python_version = "3.11"
165
+ packages = ["wtforms_htmx"]
166
+ mypy_path = "src"
167
+ ignore_missing_imports = true
168
+
169
+ [tool.tox]
170
+ requires = ["tox>=4.22", "tox-uv>=1.16"]
171
+ env_list = ["py3.11", "py3.12", "py3.13", "py3.14", "py3.15", "minversions", "typing"]
172
+
173
+ [tool.tox.env_run_base]
174
+ runner = "uv-venv-lock-runner"
175
+ dependency_groups = ["test"]
176
+ commands = [["pytest", { replace = "posargs", extend = true }]]
177
+
178
+ [tool.tox.env.typing]
179
+ runner = "uv-venv-lock-runner"
180
+ dependency_groups = ["dev"]
181
+ commands = [["mypy"]]
182
+
183
+ [tool.tox.env.coverage]
184
+ runner = "uv-venv-lock-runner"
185
+ allowlist_externals = ["npm"]
186
+ commands_pre = [["npm", "ci", "--no-audit", "--no-fund"]]
187
+ dependency_groups = ["e2e", "test"]
188
+ commands = [
189
+ ["coverage", "erase"],
190
+ ["coverage", "run", "--module", "pytest", "tests/unit", "tests/e2e", "--browser", "chromium", { replace = "posargs", extend = true }],
191
+ ["coverage", "html", "--fail-under", "0"],
192
+ ["coverage", "report", "--fail-under", "0"],
193
+ ]
194
+
195
+ [tool.tox.env.minversions]
196
+ runner = "uv-venv-runner"
197
+ base_python = ["py3.11"]
198
+ uv_resolution = "lowest-direct"
199
+ dependency_groups = ["test"]
200
+ commands = [["pytest", "tests/unit", { replace = "posargs", extend = true }]]
201
+
202
+ [tool.tox.env.e2e]
203
+ runner = "uv-venv-lock-runner"
204
+ allowlist_externals = ["npm"]
205
+ commands_pre = [["npm", "ci", "--no-audit", "--no-fund"]]
206
+ dependency_groups = ["e2e"]
207
+ commands = [["pytest", "tests/e2e", "--browser", "chromium", "--browser", "firefox", { replace = "posargs", extend = true }]]
208
+
209
+ [tool.tox.env.e2e-htmx2]
210
+ runner = "uv-venv-lock-runner"
211
+ allowlist_externals = ["npm"]
212
+ commands_pre = [["npm", "ci", "--no-audit", "--no-fund"]]
213
+ dependency_groups = ["e2e"]
214
+ commands = [["pytest", "tests/e2e", "--browser", "chromium", "--htmx-version", "2", "--ignore-glob", "tests/e2e/*_nojs.py", { replace = "posargs", extend = true }]]
@@ -0,0 +1,18 @@
1
+ """htmx integration for WTForms."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from . import wrapper # noqa: F401 (registers the wrapper= field argument)
6
+ from .form import HtmxForm
7
+ from .form import HtmxFormMixin
8
+ from .meta import HtmxMeta
9
+ from .partial import HtmxResponse
10
+ from .triggers import TriggerCallback
11
+
12
+ __all__ = [
13
+ "HtmxForm",
14
+ "HtmxFormMixin",
15
+ "HtmxMeta",
16
+ "HtmxResponse",
17
+ "TriggerCallback",
18
+ ]