flask-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,90 @@
1
+ Metadata-Version: 2.4
2
+ Name: flask-wtforms-htmx
3
+ Version: 0.1.0
4
+ Summary: A drop-in FlaskForm that brings wtforms-htmx to Flask-WTF.
5
+ Keywords: wtforms,htmx,forms,flask,validation
6
+ License-Expression: BSD-3-Clause
7
+ License-File: LICENSE.rst
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Web Environment
10
+ Classifier: Framework :: Flask
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
20
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
21
+ Requires-Dist: flask>=3.0
22
+ Requires-Dist: flask-wtf>=1.2
23
+ Requires-Dist: wtforms-htmx>=0.1.0
24
+ Maintainer: Éloi Rivard
25
+ Maintainer-email: Éloi Rivard <eloi@yaal.coop>
26
+ Requires-Python: >=3.11
27
+ Project-URL: Documentation, https://flask-wtforms-htmx.readthedocs.io/
28
+ Project-URL: Changes, https://flask-wtforms-htmx.readthedocs.io/changes/
29
+ Project-URL: Source Code, https://github.com/yaal-coop/flask-wtforms-htmx
30
+ Project-URL: Issue Tracker, https://github.com/yaal-coop/flask-wtforms-htmx/issues
31
+ Description-Content-Type: text/markdown
32
+
33
+ # flask-wtforms-htmx
34
+
35
+ A drop-in `FlaskForm` that brings
36
+ [wtforms-htmx](https://github.com/yaal-coop/wtforms-htmx) to
37
+ [Flask-WTF](https://flask-wtf.readthedocs.io/).
38
+
39
+ **Status: alpha, work in progress.**
40
+
41
+ `HtmxFlaskForm` replaces Flask-WTF's `FlaskForm`. The usual
42
+ `validate_on_submit()` view answers htmx requests, and a mutation click
43
+ without JavaScript does not save the form. CSRF protection and file
44
+ uploads work as with Flask-WTF.
45
+
46
+ ## Installation
47
+
48
+ ```sh
49
+ pip install flask-wtforms-htmx
50
+ ```
51
+
52
+ ## Example
53
+
54
+ ```python
55
+ from flask import redirect, render_template, url_for
56
+ from wtforms import ButtonField, FieldList, StringField, SubmitField
57
+ from wtforms_htmx.mutations import list_add
58
+
59
+ from flask_wtforms_htmx import HtmxFlaskForm
60
+
61
+
62
+ class TagsForm(HtmxFlaskForm):
63
+ tags = FieldList(StringField(), min_entries=1)
64
+ add_tag = ButtonField("+").triggers("tags", list_add)
65
+ save = SubmitField("Save")
66
+
67
+
68
+ @app.route("/tags", methods=["GET", "POST"])
69
+ def tags():
70
+ form = TagsForm()
71
+ if form.validate_on_submit():
72
+ save(form.data)
73
+ return redirect(url_for("tags"))
74
+
75
+ return render_template("tags.html", form=form)
76
+ ```
77
+
78
+ With htmx, a click on `+` adds a row without reloading the page. Without
79
+ JavaScript, it reloads the page with the new row, and the form is not
80
+ saved.
81
+
82
+ ## Links
83
+
84
+ - Documentation: <https://flask-wtforms-htmx.readthedocs.io/>
85
+ - Source: <https://github.com/yaal-coop/flask-wtforms-htmx>
86
+ - Issues: <https://github.com/yaal-coop/flask-wtforms-htmx/issues>
87
+
88
+ ## License
89
+
90
+ BSD-3-Clause. See `LICENSE.rst`.
@@ -0,0 +1,58 @@
1
+ # flask-wtforms-htmx
2
+
3
+ A drop-in `FlaskForm` that brings
4
+ [wtforms-htmx](https://github.com/yaal-coop/wtforms-htmx) to
5
+ [Flask-WTF](https://flask-wtf.readthedocs.io/).
6
+
7
+ **Status: alpha, work in progress.**
8
+
9
+ `HtmxFlaskForm` replaces Flask-WTF's `FlaskForm`. The usual
10
+ `validate_on_submit()` view answers htmx requests, and a mutation click
11
+ without JavaScript does not save the form. CSRF protection and file
12
+ uploads work as with Flask-WTF.
13
+
14
+ ## Installation
15
+
16
+ ```sh
17
+ pip install flask-wtforms-htmx
18
+ ```
19
+
20
+ ## Example
21
+
22
+ ```python
23
+ from flask import redirect, render_template, url_for
24
+ from wtforms import ButtonField, FieldList, StringField, SubmitField
25
+ from wtforms_htmx.mutations import list_add
26
+
27
+ from flask_wtforms_htmx import HtmxFlaskForm
28
+
29
+
30
+ class TagsForm(HtmxFlaskForm):
31
+ tags = FieldList(StringField(), min_entries=1)
32
+ add_tag = ButtonField("+").triggers("tags", list_add)
33
+ save = SubmitField("Save")
34
+
35
+
36
+ @app.route("/tags", methods=["GET", "POST"])
37
+ def tags():
38
+ form = TagsForm()
39
+ if form.validate_on_submit():
40
+ save(form.data)
41
+ return redirect(url_for("tags"))
42
+
43
+ return render_template("tags.html", form=form)
44
+ ```
45
+
46
+ With htmx, a click on `+` adds a row without reloading the page. Without
47
+ JavaScript, it reloads the page with the new row, and the form is not
48
+ saved.
49
+
50
+ ## Links
51
+
52
+ - Documentation: <https://flask-wtforms-htmx.readthedocs.io/>
53
+ - Source: <https://github.com/yaal-coop/flask-wtforms-htmx>
54
+ - Issues: <https://github.com/yaal-coop/flask-wtforms-htmx/issues>
55
+
56
+ ## License
57
+
58
+ BSD-3-Clause. See `LICENSE.rst`.
@@ -0,0 +1,193 @@
1
+ [project]
2
+ name = "flask-wtforms-htmx"
3
+ version = "0.1.0"
4
+ description = "A drop-in FlaskForm that brings wtforms-htmx to Flask-WTF."
5
+ readme = "README.md"
6
+ license = "BSD-3-Clause"
7
+ license-files = ["LICENSE.rst"]
8
+ keywords = [
9
+ "wtforms",
10
+ "htmx",
11
+ "forms",
12
+ "flask",
13
+ "validation",
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Environment :: Web Environment",
18
+ "Framework :: Flask",
19
+ "Intended Audience :: Developers",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Programming Language :: Python :: 3.15",
28
+ "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
29
+ ]
30
+ requires-python = ">=3.11"
31
+ dependencies = [
32
+ "flask>=3.0",
33
+ "flask-wtf>=1.2",
34
+ "wtforms-htmx>=0.1.0",
35
+ ]
36
+
37
+ [[project.maintainers]]
38
+ name = "Éloi Rivard"
39
+ email = "eloi@yaal.coop"
40
+
41
+ [project.urls]
42
+ Documentation = "https://flask-wtforms-htmx.readthedocs.io/"
43
+ Changes = "https://flask-wtforms-htmx.readthedocs.io/changes/"
44
+ "Source Code" = "https://github.com/yaal-coop/flask-wtforms-htmx"
45
+ "Issue Tracker" = "https://github.com/yaal-coop/flask-wtforms-htmx/issues"
46
+
47
+ [build-system]
48
+ requires = ["uv_build>=0.13.1,<0.14"]
49
+ build-backend = "uv_build"
50
+
51
+ [tool.uv]
52
+ required-version = ">=0.13.1"
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests/unit"]
56
+ pythonpath = ["."]
57
+ filterwarnings = ["error"]
58
+
59
+ [tool.coverage.run]
60
+ branch = true
61
+ source = [
62
+ "flask_wtforms_htmx",
63
+ "tests",
64
+ ]
65
+
66
+ [tool.coverage.paths]
67
+ source = [
68
+ "src",
69
+ "*/site-packages",
70
+ ]
71
+
72
+ [tool.coverage.report]
73
+ fail_under = 100
74
+ exclude_lines = [
75
+ "pragma: no cover",
76
+ "except ImportError",
77
+ "if TYPE_CHECKING:",
78
+ ]
79
+
80
+ [tool.ruff]
81
+ src = ["src"]
82
+ fix = true
83
+ show-fixes = true
84
+ output-format = "full"
85
+
86
+ [tool.ruff.lint]
87
+ select = [
88
+ "B",
89
+ "C90",
90
+ "D",
91
+ "E",
92
+ "F",
93
+ "I",
94
+ "UP",
95
+ "W",
96
+ "SIM",
97
+ "RET",
98
+ "BLE",
99
+ "TRY",
100
+ "ERA",
101
+ "PIE",
102
+ "RUF",
103
+ "DTZ",
104
+ "PTH",
105
+ ]
106
+ ignore = [
107
+ "E501",
108
+ "E722",
109
+ "D100",
110
+ "D101",
111
+ "D102",
112
+ "D103",
113
+ "D104",
114
+ "D105",
115
+ "D106",
116
+ "D107",
117
+ "D203",
118
+ "D213",
119
+ "RUF001",
120
+ "RUF002",
121
+ "RUF003",
122
+ "TRY003",
123
+ ]
124
+
125
+ [tool.ruff.lint.isort]
126
+ force-single-line = true
127
+ order-by-type = false
128
+
129
+ [tool.ruff.lint.mccabe]
130
+ max-complexity = 10
131
+
132
+ [tool.mypy]
133
+ python_version = "3.11"
134
+ packages = ["flask_wtforms_htmx"]
135
+ mypy_path = "src"
136
+ ignore_missing_imports = true
137
+
138
+ [tool.tox]
139
+ requires = [
140
+ "tox>=4.22",
141
+ "tox-uv>=1.16",
142
+ ]
143
+ env_list = [
144
+ "py3.11",
145
+ "py3.12",
146
+ "py3.13",
147
+ "py3.14",
148
+ "py3.15",
149
+ "minversions",
150
+ "typing",
151
+ ]
152
+
153
+ [tool.tox.env_run_base]
154
+ runner = "uv-venv-lock-runner"
155
+ dependency_groups = ["test"]
156
+ commands = [[
157
+ "pytest",
158
+ { replace = "posargs", extend = true },
159
+ ]]
160
+
161
+ [tool.tox.env.typing]
162
+ runner = "uv-venv-lock-runner"
163
+ dependency_groups = ["dev"]
164
+ commands = [["mypy"]]
165
+
166
+ [tool.tox.env.minversions]
167
+ runner = "uv-venv-runner"
168
+ base_python = ["py3.11"]
169
+ uv_resolution = "lowest-direct"
170
+ dependency_groups = ["test"]
171
+ commands = [[
172
+ "pytest",
173
+ "tests/unit",
174
+ { replace = "posargs", extend = true },
175
+ ]]
176
+
177
+ [dependency-groups]
178
+ doc = [
179
+ "pallets-sphinx-themes>=2.5.0",
180
+ "sphinx>=8.1.0",
181
+ "sphinx-issues>=6.0.0",
182
+ ]
183
+ test = [
184
+ "pytest>=8.0",
185
+ "pytest-cov>=5.0",
186
+ ]
187
+ dev = [
188
+ { include-group = "test" },
189
+ "mypy>=1.11",
190
+ "prek>=0.3.9",
191
+ "pytest-xdist>=3.8.0",
192
+ "tox-uv>=1.35.1",
193
+ ]
@@ -0,0 +1,163 @@
1
+ [project]
2
+ name = "flask-wtforms-htmx"
3
+ version = "0.1.0"
4
+ description = "A drop-in FlaskForm that brings wtforms-htmx to Flask-WTF."
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", "flask", "validation"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Environment :: Web Environment",
15
+ "Framework :: Flask",
16
+ "Intended Audience :: Developers",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Programming Language :: Python :: 3.15",
25
+ "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
26
+ ]
27
+ requires-python = ">=3.11"
28
+ dependencies = [
29
+ "flask>=3.0",
30
+ "flask-wtf>=1.2",
31
+ "wtforms-htmx>=0.1.0",
32
+ ]
33
+
34
+ [project.urls]
35
+ Documentation = "https://flask-wtforms-htmx.readthedocs.io/"
36
+ Changes = "https://flask-wtforms-htmx.readthedocs.io/changes/"
37
+ "Source Code" = "https://github.com/yaal-coop/flask-wtforms-htmx"
38
+ "Issue Tracker" = "https://github.com/yaal-coop/flask-wtforms-htmx/issues"
39
+
40
+ [build-system]
41
+ requires = ["uv_build>=0.13.1,<0.14"]
42
+ build-backend = "uv_build"
43
+
44
+ [tool.uv]
45
+ required-version = ">=0.13.1"
46
+
47
+ [dependency-groups]
48
+ doc = [
49
+ "pallets-sphinx-themes>=2.5.0",
50
+ "sphinx>=8.1.0",
51
+ "sphinx-issues>=6.0.0",
52
+ ]
53
+ test = [
54
+ "pytest>=8.0",
55
+ "pytest-cov>=5.0",
56
+ ]
57
+ dev = [
58
+ { include-group = "test" },
59
+ "mypy>=1.11",
60
+ "prek>=0.3.9",
61
+ "pytest-xdist>=3.8.0",
62
+ "tox-uv>=1.35.1",
63
+ ]
64
+
65
+ [tool.pytest.ini_options]
66
+ testpaths = ["tests/unit"]
67
+ pythonpath = ["."]
68
+ filterwarnings = ["error"]
69
+
70
+ [tool.coverage.run]
71
+ branch = true
72
+ source = ["flask_wtforms_htmx", "tests"]
73
+
74
+ [tool.coverage.paths]
75
+ source = ["src", "*/site-packages"]
76
+
77
+ [tool.coverage.report]
78
+ fail_under = 100
79
+ exclude_lines = [
80
+ "pragma: no cover",
81
+ "except ImportError",
82
+ "if TYPE_CHECKING:",
83
+ ]
84
+
85
+ [tool.ruff]
86
+ src = ["src"]
87
+ fix = true
88
+ show-fixes = true
89
+ output-format = "full"
90
+
91
+ [tool.ruff.lint]
92
+ select = [
93
+ "B", # flake8-bugbear
94
+ "C90", # mccabe
95
+ "D", # pydocstyle
96
+ "E", # pycodestyle error
97
+ "F", # pyflakes
98
+ "I", # isort
99
+ "UP", # pyupgrade
100
+ "W", # pycodestyle warning
101
+ "SIM",
102
+ "RET",
103
+ "BLE",
104
+ "TRY",
105
+ "ERA",
106
+ "PIE",
107
+ "RUF",
108
+ "DTZ",
109
+ "PTH",
110
+ ]
111
+
112
+ ignore = [
113
+ "E501", # line-too-long
114
+ "E722", # bare-except
115
+ "D100", # public module
116
+ "D101", # public class
117
+ "D102", # public method
118
+ "D103", # public function
119
+ "D104", # public package
120
+ "D105", # magic method
121
+ "D106", # nested class
122
+ "D107", # public init
123
+ "D203", # no-blank-line-before-class
124
+ "D213", # multi-line-summary-second-line
125
+ "RUF001",
126
+ "RUF002",
127
+ "RUF003",
128
+ "TRY003", # raise-vanilla-args — dedicated exception classes would be overkill here
129
+ ]
130
+
131
+ [tool.ruff.lint.isort]
132
+ force-single-line = true
133
+ order-by-type = false
134
+
135
+ [tool.ruff.lint.mccabe]
136
+ max-complexity = 10
137
+
138
+ [tool.mypy]
139
+ python_version = "3.11"
140
+ packages = ["flask_wtforms_htmx"]
141
+ mypy_path = "src"
142
+ ignore_missing_imports = true
143
+
144
+ [tool.tox]
145
+ requires = ["tox>=4.22", "tox-uv>=1.16"]
146
+ env_list = ["py3.11", "py3.12", "py3.13", "py3.14", "py3.15", "minversions", "typing"]
147
+
148
+ [tool.tox.env_run_base]
149
+ runner = "uv-venv-lock-runner"
150
+ dependency_groups = ["test"]
151
+ commands = [["pytest", { replace = "posargs", extend = true }]]
152
+
153
+ [tool.tox.env.typing]
154
+ runner = "uv-venv-lock-runner"
155
+ dependency_groups = ["dev"]
156
+ commands = [["mypy"]]
157
+
158
+ [tool.tox.env.minversions]
159
+ runner = "uv-venv-runner"
160
+ base_python = ["py3.11"]
161
+ uv_resolution = "lowest-direct"
162
+ dependency_groups = ["test"]
163
+ commands = [["pytest", "tests/unit", { replace = "posargs", extend = true }]]
@@ -0,0 +1,119 @@
1
+ """A drop-in FlaskForm that brings wtforms-htmx to Flask-WTF."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from typing import TYPE_CHECKING
7
+
8
+ from flask import abort
9
+ from flask import current_app
10
+ from flask import has_request_context
11
+ from flask import render_template
12
+ from flask import request
13
+ from flask import Response
14
+ from flask_wtf import FlaskForm
15
+ from flask_wtf.csrf import CSRFError
16
+ from markupsafe import Markup
17
+ from wtforms_htmx import HtmxFormMixin
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Callable
21
+
22
+
23
+ class HtmxFlaskForm(HtmxFormMixin, FlaskForm):
24
+ """Drop-in replacement for :class:`~flask_wtf.FlaskForm` that answers htmx requests.
25
+
26
+ The form reads the request headers itself, so the view does not pass
27
+ ``headers=``.
28
+
29
+ With an invalid CSRF token, htmx requests and clicks on a button with
30
+ :meth:`.triggers() <wtforms.fields.core.UnboundField.triggers>` get a
31
+ 400 response (:class:`~flask_wtf.csrf.CSRFError`), and no callback runs.
32
+ htmx leaves the page as it is. A plain submit shows the error in the
33
+ form, as with Flask-WTF.
34
+ """
35
+
36
+ def __init__(self, *args, headers=None, **kwargs):
37
+ if headers is None and has_request_context():
38
+ headers = request.headers
39
+
40
+ super().__init__(*args, headers=headers, **kwargs)
41
+
42
+ def mutate(self) -> None:
43
+ """Run the mutations, or abort with a 400 when the CSRF token blocks them."""
44
+ super().mutate()
45
+ if not self.meta.csrf:
46
+ return
47
+
48
+ errors = self[self.meta.csrf_field_name].errors
49
+ if not errors or not self._is_interaction():
50
+ return
51
+
52
+ # htmx 4 swaps 4xx responses by default: keep the page as it is.
53
+ response = Response(errors[0], status=400, headers={"HX-Reswap": "none"})
54
+ raise CSRFError(errors[0], response=response)
55
+
56
+ def _is_interaction(self) -> bool:
57
+ """Return True for an htmx request or a click on a button with triggers."""
58
+ if self.submitter is not None and self.htmx_submit_attrs(self.submitter):
59
+ return True
60
+
61
+ return "HX-Request" in request.headers and "HX-Boosted" not in request.headers
62
+
63
+ def validate_on_submit(
64
+ self, extra_validators=None, *, submitter: Any = None
65
+ ) -> bool:
66
+ """Validate the form when the user saves it.
67
+
68
+ On an htmx request with a partial response, :func:`flask.abort` sends
69
+ that response, so the rest of the view does not run.
70
+
71
+ Returns ``False`` when the form was not submitted, or when a button
72
+ with :meth:`.triggers() <wtforms.fields.core.UnboundField.triggers>`
73
+ submitted it without JavaScript. With ``submitter=form.save``, only
74
+ that button saves.
75
+ """
76
+ if result := self.partial():
77
+ abort(Response(result.body, headers=result.headers))
78
+
79
+ if not self.is_submitted():
80
+ return False
81
+
82
+ if submitter is not None and self.submitter is not submitter:
83
+ return False
84
+
85
+ if self.mutated:
86
+ return False
87
+
88
+ return self.validate(extra_validators)
89
+
90
+
91
+ def jinja_renderer(template: str, macro: str | None = None) -> Callable:
92
+ """Build a field wrapper callable that renders a Jinja template or macro.
93
+
94
+ ``jinja_renderer("_field.html")`` renders the template with ``field`` and
95
+ ``render_kw`` in its context. ``jinja_renderer("_macros.html", "render_field")``
96
+ calls the macro with ``(field, render_kw)``::
97
+
98
+ class ProfileForm(HtmxFlaskForm):
99
+ class Meta:
100
+ field_wrapper = staticmethod(jinja_renderer("_field.html"))
101
+ """
102
+ if macro is None:
103
+
104
+ def render_template_fn(field, render_kw):
105
+ return render_template(template, field=field, render_kw=render_kw)
106
+
107
+ return render_template_fn
108
+
109
+ def render_macro_fn(field, render_kw):
110
+ module = current_app.jinja_env.get_template(template).module
111
+ return Markup(getattr(module, macro)(field, render_kw))
112
+
113
+ return render_macro_fn
114
+
115
+
116
+ __all__ = [
117
+ "HtmxFlaskForm",
118
+ "jinja_renderer",
119
+ ]