htpy-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,52 @@
1
+ name: tests
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+
7
+ jobs:
8
+ tests:
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ include:
13
+ - name: pytest (3.10)
14
+ python: "3.10"
15
+ - name: pytest (3.11)
16
+ python: "3.11"
17
+ - name: pytest (3.12)
18
+ python: "3.12"
19
+ - name: pytest (3.13)
20
+ python: "3.13"
21
+ - name: pytest (3.14)
22
+ python: "3.14"
23
+ name: ${{ matrix.name }}
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v7
27
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
28
+ with:
29
+ python-version: ${{ matrix.python }}
30
+ activate-environment: true
31
+ - run: uv sync
32
+ - run: uv run --python ${{ matrix.python }} pytest
33
+
34
+ mypy:
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v7
38
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
39
+ with:
40
+ python-version: "3.10"
41
+ - run: uv sync
42
+ - run: uv run mypy .
43
+
44
+ black:
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - uses: actions/checkout@v7
48
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
49
+ with:
50
+ python-version: "3.10"
51
+ - run: uv sync
52
+ - run: uv run black --check .
@@ -0,0 +1,6 @@
1
+ .idea
2
+ __pycache__
3
+ .venv
4
+ build
5
+ *.egg-info
6
+ dist
@@ -0,0 +1,20 @@
1
+ # See https://pre-commit.com for more information
2
+ # See https://pre-commit.com/hooks.html for more hooks
3
+ repos:
4
+ - repo: https://github.com/psf/black
5
+ rev: 26.5.1
6
+ hooks:
7
+ - id: black
8
+ - repo: https://github.com/pre-commit/mirrors-mypy
9
+ rev: v2.3.0
10
+ hooks:
11
+ - id: mypy
12
+ - repo: local
13
+ hooks:
14
+ - id: pytest
15
+ name: pytest
16
+ entry: ./.venv/bin/pytest
17
+ language: system
18
+ types: [python]
19
+ pass_filenames: false
20
+ always_run: true
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 August Malmdahl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: htpy-htmx
3
+ Version: 0.1.0
4
+ Summary: Helpers for using htmx in htpy
5
+ Author-email: August Malmdahl <august.malmdahl@gmail.com>
6
+ Project-URL: Repository, https://github.com/augustifolia/htpy-htmx
7
+ Project-URL: Issues, https://github.com/augustifolia/htpy-htmx/issues
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Typing :: Typed
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENCE
17
+ Requires-Dist: htpy
18
+ Dynamic: license-file
19
+
20
+ # htpy-htmx
21
+
22
+ htpy-htmx is a library to make it easier to work with [htmx](https://four.htmx.org/) in [htpy](https://htpy.dev/).
23
+
24
+ This library is intended to work with htmx 4.
25
+
26
+ Note: This project is still a work in progress and there may be breaking changes.
27
+
28
+ ## Usage
29
+
30
+ You can import htpy-htmx as htpy_htmx.hx. All normal htmx attributes are available from the module,
31
+ but without the "hx-" prefix. So `hx-get="url"` would become `hx.get("url")`.
32
+
33
+ Some htmx extension attributes are available in the htpy_htmx.ext module.
34
+
35
+ ```python
36
+ import htpy as h
37
+ from htpy_htmx import hx
38
+ from htpy_htmx import ext
39
+
40
+ # pass htmx attributes as functions
41
+ h.div("#div-id", hx.get("/some-url/"), hx.trigger("click"))
42
+
43
+ # hx-partial is also available as a htpy element
44
+ h.div("#div-id")[
45
+ h.h2["header"],
46
+ hx.partial("#part-id", hx.target("#some-element"))[
47
+ h.div("#div1")[
48
+ h.p["part one"]
49
+ ]
50
+ ],
51
+ ]
52
+
53
+ h.div(
54
+ # trigger allows a list of trigger events
55
+ hx.trigger("blur", "keyup[key=='Enter']"),
56
+ # swap takes a swap style as string and modifiers as keyword arguments
57
+ hx.swap("before", swap="1s", scroll="bottom"),
58
+ # hx-on takes an event and a JavaScript expression
59
+ hx.on("htmx:before:request", "showSpinner()"),
60
+ # vals takes a dict of data.
61
+ # To evaluate JavaScript, wrap your js expression in hx.JS() and pass js=True.
62
+ hx.vals({"foo": "bar", "some_js": hx.JS("getSomeData()")}, js=True),
63
+ )
64
+
65
+ # to enable inheritance pass inherited=True to a hx-function
66
+ h.div(hx.confirm("Are you sure?", inherited=True))[
67
+ h.button(hx.delete("/account"))["Delete My Account"],
68
+ h.button(hx.put("/account"))["Update My Account"],
69
+ ]
70
+
71
+ # Some extension attributes are available in ext:
72
+ h.div(
73
+ ext.preload("touchstart"),
74
+ ext.optimistic("#msg-opt"),
75
+ ext.ws(connect="click"),
76
+ )
77
+ ```
78
+
79
+ ## Installation
80
+
81
+ To install with pip run:
82
+
83
+ pip install htpy-htmx
@@ -0,0 +1,64 @@
1
+ # htpy-htmx
2
+
3
+ htpy-htmx is a library to make it easier to work with [htmx](https://four.htmx.org/) in [htpy](https://htpy.dev/).
4
+
5
+ This library is intended to work with htmx 4.
6
+
7
+ Note: This project is still a work in progress and there may be breaking changes.
8
+
9
+ ## Usage
10
+
11
+ You can import htpy-htmx as htpy_htmx.hx. All normal htmx attributes are available from the module,
12
+ but without the "hx-" prefix. So `hx-get="url"` would become `hx.get("url")`.
13
+
14
+ Some htmx extension attributes are available in the htpy_htmx.ext module.
15
+
16
+ ```python
17
+ import htpy as h
18
+ from htpy_htmx import hx
19
+ from htpy_htmx import ext
20
+
21
+ # pass htmx attributes as functions
22
+ h.div("#div-id", hx.get("/some-url/"), hx.trigger("click"))
23
+
24
+ # hx-partial is also available as a htpy element
25
+ h.div("#div-id")[
26
+ h.h2["header"],
27
+ hx.partial("#part-id", hx.target("#some-element"))[
28
+ h.div("#div1")[
29
+ h.p["part one"]
30
+ ]
31
+ ],
32
+ ]
33
+
34
+ h.div(
35
+ # trigger allows a list of trigger events
36
+ hx.trigger("blur", "keyup[key=='Enter']"),
37
+ # swap takes a swap style as string and modifiers as keyword arguments
38
+ hx.swap("before", swap="1s", scroll="bottom"),
39
+ # hx-on takes an event and a JavaScript expression
40
+ hx.on("htmx:before:request", "showSpinner()"),
41
+ # vals takes a dict of data.
42
+ # To evaluate JavaScript, wrap your js expression in hx.JS() and pass js=True.
43
+ hx.vals({"foo": "bar", "some_js": hx.JS("getSomeData()")}, js=True),
44
+ )
45
+
46
+ # to enable inheritance pass inherited=True to a hx-function
47
+ h.div(hx.confirm("Are you sure?", inherited=True))[
48
+ h.button(hx.delete("/account"))["Delete My Account"],
49
+ h.button(hx.put("/account"))["Update My Account"],
50
+ ]
51
+
52
+ # Some extension attributes are available in ext:
53
+ h.div(
54
+ ext.preload("touchstart"),
55
+ ext.optimistic("#msg-opt"),
56
+ ext.ws(connect="click"),
57
+ )
58
+ ```
59
+
60
+ ## Installation
61
+
62
+ To install with pip run:
63
+
64
+ pip install htpy-htmx
File without changes
@@ -0,0 +1,107 @@
1
+ from typing import TypeAlias, Literal
2
+
3
+ _swap_style: TypeAlias = (
4
+ str # This allows extensions to add new swap_styles (like upsert and download)
5
+ | Literal[
6
+ "innerHTML",
7
+ "outerHTML",
8
+ "beforebegin",
9
+ "before",
10
+ "afterbegin",
11
+ "prepend",
12
+ "beforeend",
13
+ "append",
14
+ "afterend",
15
+ "after",
16
+ "delete",
17
+ "none",
18
+ "innerMorph",
19
+ "outerMorph",
20
+ "textContent",
21
+ "outerSync",
22
+ # "upsert", # Requires the hx-upsert extension.
23
+ # "download", # Required the hx-download extension.
24
+ ]
25
+ )
26
+
27
+ _selector: TypeAlias = (
28
+ str | Literal["this", "next", "previous", "body", "document", "window", "host"]
29
+ )
30
+
31
+ _event: TypeAlias = (
32
+ str
33
+ | Literal[
34
+ "load",
35
+ "revealed",
36
+ "intersect",
37
+ "submit",
38
+ "change",
39
+ "input",
40
+ "focus",
41
+ "blur",
42
+ "click",
43
+ "dblclick",
44
+ "mousedown",
45
+ "mouseup",
46
+ "mousemove",
47
+ "mouseenter",
48
+ "mouseleave",
49
+ "wheel",
50
+ "mousewheel",
51
+ "keyup",
52
+ "keydown",
53
+ "keypress",
54
+ "dragstart",
55
+ "drag",
56
+ "dragend",
57
+ "dragenter",
58
+ "dragover",
59
+ "dragleave",
60
+ "drop",
61
+ "play",
62
+ "pause",
63
+ "ended",
64
+ "volumechange",
65
+ "touchstart",
66
+ "touchend",
67
+ "touchmove",
68
+ "touchcancel",
69
+ "resize",
70
+ "scroll",
71
+ "select",
72
+ "search",
73
+ "invalid",
74
+ "contextmenu",
75
+ "reset",
76
+ ]
77
+ )
78
+
79
+ _htmx_events: TypeAlias = Literal[
80
+ "htmx:config:request",
81
+ "htmx:before:request",
82
+ "htmx:after:request",
83
+ "htmx:finally:request",
84
+ "htmx:before:swap",
85
+ "htmx:after:swap",
86
+ "htmx:finally:swap",
87
+ "htmx:before:cleanup",
88
+ "htmx:after:cleanup",
89
+ "htmx:confirm",
90
+ "htmx:error",
91
+ "htmx:abort",
92
+ "htmx:before:init",
93
+ "htmx:after:init",
94
+ "htmx:before:process",
95
+ "htmx:after:process",
96
+ "htmx:before:history:update",
97
+ "htmx:after:history:update",
98
+ "htmx:after:history:push",
99
+ "htmx:after:history:replace",
100
+ "htmx:before:history:restore",
101
+ "htmx:before:viewTransition",
102
+ "htmx:after:viewTransition",
103
+ "htmx:before:response",
104
+ "htmx:before:settle",
105
+ "htmx:after:settle",
106
+ "htmx:response:error",
107
+ ]
@@ -0,0 +1,129 @@
1
+ from __future__ import annotations
2
+ from typing import Literal
3
+ from ._types import _selector, _event
4
+
5
+
6
+ def multipart(*, connect: str, close: _event, inherited: bool = False) -> dict[str, str]:
7
+ """Stream HTML with multipart/mixed.
8
+
9
+ https://four.htmx.org/extensions/hx-multipart
10
+ """
11
+ data: dict[str, str] = {}
12
+ if connect:
13
+ data[f"hx-multipart:connect{':inherited' if inherited else ''}"] = connect
14
+ if close:
15
+ data[f"hx-multipart:close{':inherited' if inherited else ''}"] = close
16
+ return data
17
+
18
+
19
+ def sse(*, connect: _event = "", close: _event = "", inherited: bool = False) -> dict[str, str]:
20
+ """Stream HTML with text/event-stream (SSE).
21
+
22
+ https://four.htmx.org/extensions/hx-sse
23
+ """
24
+ data: dict[str, str] = {}
25
+ if connect:
26
+ data[f"hx-sse:connect{':inherited' if inherited else ''}"] = connect
27
+ if close:
28
+ data[f"hx-sse:close{':inherited' if inherited else ''}"] = close
29
+ return data
30
+
31
+
32
+ def ws(*, connect: _event = "", send: bool = False, inherited: bool = False) -> dict[str, str]:
33
+ """Stream HTML and send data over WebSockets.
34
+
35
+ https://four.htmx.org/extensions/hx-ws
36
+ """
37
+ data: dict[str, str] = {}
38
+ if connect:
39
+ data[f"hx-ws:connect{':inherited' if inherited else ''}"] = connect
40
+ if send:
41
+ data[f"hx-ws:send{':inherited' if inherited else ''}"] = ""
42
+ return data
43
+
44
+
45
+ def live(attribute: str, expression: str, *, inherited: bool = False) -> dict[str, str]:
46
+ """Add reactive bindings to HTML.
47
+
48
+ https://four.htmx.org/extensions/hx-live
49
+ """
50
+ return {f"hx-live:{attribute}{':inherited' if inherited else ''}": expression}
51
+
52
+
53
+ def optimistic(selector: _selector, *, inherited: bool = False) -> dict[str, str]:
54
+ """Shows optimistic content during request.
55
+
56
+ Note: This is an extension attribute. To use it, you must include the optimistic extension.
57
+ https://four.htmx.org/reference/attributes/hx-optimistic
58
+ """
59
+ return {f"hx-optimistic{':inherited' if inherited else ''}": selector}
60
+
61
+
62
+ def browser_indicator(show_indicator: bool = True, *, inherited: bool = False) -> dict[str, str]:
63
+ """Show tab's spinner with hx-browser-indicator.
64
+
65
+ https://four.htmx.org/extensions/hx-browser-indicator
66
+ """
67
+ return {f"hx-browser-indicator{':inherited' if inherited else ''}": str(show_indicator).lower()}
68
+
69
+
70
+ def prompt(prompt: str, *, inherited: bool = False) -> dict[str, str]:
71
+ """Prompt before requests with hx-prompt='Reason?'
72
+
73
+ https://four.htmx.org/extensions/hx-prompt
74
+ """
75
+ return {f"hx-prompt{':inherited' if inherited else ''}": prompt}
76
+
77
+
78
+ def preload(
79
+ event: Literal["mouseenter", "mouseover", "touchstart"], *, inherited: bool = False
80
+ ) -> dict[str, str]:
81
+ """Preloads content before user triggers request.
82
+
83
+ Note: This is an extension attribute. To use it, you must include the preload extension.
84
+ https://four.htmx.org/reference/attributes/hx-preload
85
+ """
86
+ return {f"hx-preload{':inherited' if inherited else ''}": event}
87
+
88
+
89
+ def ptag(tag: str, *, inherited: bool = False) -> dict[str, str]:
90
+ """Skip unchanged polls with HX-PTag: "v42".
91
+
92
+ https://four.htmx.org/extensions/hx-ptag
93
+ """
94
+ return {f"hx-ptag{':inherited' if inherited else ''}": tag}
95
+
96
+
97
+ def history(save_to_cache: bool, *, inherited: bool = False) -> dict[str, str]:
98
+ """Restore back/forward pages from sessionStorage.
99
+
100
+ https://four.htmx.org/extensions/hx-history-cache
101
+ """
102
+ return {f"hx-history{':inherited' if inherited else ''}": str(save_to_cache).lower()}
103
+
104
+
105
+ def head(
106
+ strategy: Literal["merge", "append", "re-eval"], *, inherited: bool = False
107
+ ) -> dict[str, str]:
108
+ """Merge <head> tags with hx-head='merge'.
109
+
110
+ https://four.htmx.org/extensions/hx-head
111
+ """
112
+ return {f"hx-head{':inherited' if inherited else ''}": strategy}
113
+
114
+
115
+ def targets(*selector: _selector, inherited: bool = False) -> dict[str, str]:
116
+ """Target many elements.
117
+
118
+ Requires the hx-targets extension
119
+ https://four.htmx.org/extensions/hx-targets
120
+ """
121
+ return {f"hx-targets{':inherited' if inherited else ''}": ", ".join(selector)}
122
+
123
+
124
+ def nonce(csp_nonce: str, *, inherited: bool = False) -> dict[str, str]:
125
+ """Make htmx work under strict Content Security Policy.
126
+
127
+ https://four.htmx.org/extensions/hx-csp
128
+ """
129
+ return {f"hx-nonce{':inherited' if inherited else ''}": csp_nonce}