htpy-htmx 0.1.0__py3-none-any.whl

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.
htpy_htmx/__init__.py ADDED
File without changes
htpy_htmx/_types.py ADDED
@@ -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
+ ]
htpy_htmx/ext.py ADDED
@@ -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}
htpy_htmx/hx.py ADDED
@@ -0,0 +1,509 @@
1
+ from __future__ import annotations
2
+ import json
3
+ from typing import Literal, Any, overload
4
+ from htpy import Element
5
+ from ._types import _swap_style, _selector, _event, _htmx_events
6
+
7
+ partial = Element("hx-partial")
8
+
9
+
10
+ def get(url: str, *, inherited: bool = False) -> dict[str, str]:
11
+ """Issues GET request to specified URL.
12
+
13
+ https://four.htmx.org/reference/attributes/hx-get
14
+ """
15
+ return {f"hx-get{':inherited' if inherited else ''}": url}
16
+
17
+
18
+ def post(url: str, *, inherited: bool = False) -> dict[str, str]:
19
+ """Issues POST request to specified URL.
20
+
21
+ https://four.htmx.org/reference/attributes/hx-post
22
+ """
23
+ return {f"hx-post{':inherited' if inherited else ''}": url}
24
+
25
+
26
+ def put(url: str, *, inherited: bool = False) -> dict[str, str]:
27
+ """Issues PUT request to specified URL.
28
+
29
+ https://four.htmx.org/reference/attributes/hx-put
30
+ """
31
+ return {f"hx-put{':inherited' if inherited else ''}": url}
32
+
33
+
34
+ def patch(url: str, *, inherited: bool = False) -> dict[str, str]:
35
+ """Issues PATCH request to specified URL.
36
+
37
+ https://four.htmx.org/reference/attributes/hx-patch
38
+ """
39
+ return {f"hx-patch{':inherited' if inherited else ''}": url}
40
+
41
+
42
+ def delete(url: str, *, inherited: bool = False) -> dict[str, str]:
43
+ """Issues DELETE request to specified URL.
44
+
45
+ https://four.htmx.org/reference/attributes/hx-delete
46
+ """
47
+ return {f"hx-delete{':inherited' if inherited else ''}": url}
48
+
49
+
50
+ def trigger_event(
51
+ event: _event,
52
+ filter: str = "",
53
+ once: bool = False,
54
+ changed: bool = False,
55
+ delay: str = "",
56
+ throttle: str = "",
57
+ from_: _selector = "",
58
+ target: _selector = "",
59
+ prevent: bool = False,
60
+ stop: bool = False,
61
+ consume: bool = False,
62
+ halt: bool = False,
63
+ capture: bool = False,
64
+ passive: bool = False,
65
+ ) -> str:
66
+ """Helper function to generate event syntax for hx-trigger.
67
+
68
+ Usage:
69
+ hx.trigger(hx.trigger_event("keyup", filter="key==Enter"), "click")
70
+ """
71
+ text: str = event
72
+ if filter:
73
+ text += f"[{filter}]"
74
+ if once:
75
+ text += " once"
76
+ if changed:
77
+ text += " changed"
78
+ if delay:
79
+ text += f" delay:{delay}"
80
+ if throttle:
81
+ text += f" throttle:{throttle}"
82
+ if from_:
83
+ text += f" from:{from_}"
84
+ if target:
85
+ text += f" target:{target}"
86
+ if prevent:
87
+ text += " prevent"
88
+ if stop:
89
+ text += " stop"
90
+ if consume:
91
+ text += " consume"
92
+ if halt:
93
+ text += " halt"
94
+ if capture:
95
+ text += " capture"
96
+ if passive:
97
+ text += " passive"
98
+ return text
99
+
100
+
101
+ def trigger(*event: _event, inherited: bool = False) -> dict[str, str]:
102
+ """Controls when element issues requests.
103
+
104
+ https://four.htmx.org/reference/attributes/hx-trigger
105
+ """
106
+ return {f"hx-trigger{':inherited' if inherited else ''}": ", ".join(event)}
107
+
108
+
109
+ def swap(
110
+ style: _swap_style,
111
+ *,
112
+ transition: bool | None = None,
113
+ swap: str = "",
114
+ settle: str = "",
115
+ ignoreTitle: bool | None = None,
116
+ scroll: Literal["top", "bottom"] | str = "",
117
+ scrollTarget: str = "",
118
+ show: Literal["top", "bottom", "none"] | str = "",
119
+ showTarget: str = "",
120
+ focusScroll: bool | None = None,
121
+ target: str = "",
122
+ strip: bool | None = None,
123
+ swapEmpty: bool | None = None,
124
+ inherited: bool = False,
125
+ ) -> dict[str, str]:
126
+ """Controls how response is inserted.
127
+
128
+ https://four.htmx.org/reference/attributes/hx-swap
129
+ """
130
+ text: str = style
131
+ if transition is not None:
132
+ text += f" transition:{str(transition).lower()}"
133
+ if swap:
134
+ text += f" swap:{swap}"
135
+ if settle:
136
+ text += f" settle:{settle}"
137
+ if ignoreTitle is not None:
138
+ text += f" ignoreTitle:{str(ignoreTitle).lower()}"
139
+ if scroll:
140
+ text += f" scroll:{scroll}"
141
+ if scrollTarget:
142
+ text += f" scrollTarget:{scrollTarget}"
143
+ if show:
144
+ text += f" show:{show}"
145
+ if showTarget:
146
+ text += f" showTarget:{showTarget}"
147
+ if focusScroll is not None:
148
+ text += f" focusScroll:{str(focusScroll).lower()}"
149
+ if target:
150
+ text += f" target:{target}"
151
+ if strip is not None:
152
+ text += f" strip:{str(strip).lower()}"
153
+ if swapEmpty is not None:
154
+ text += f" swapEmpty:{str(swapEmpty).lower()}"
155
+ return {f"hx-swap{':inherited' if inherited else ''}": text}
156
+
157
+
158
+ def morph_skip(*, inherited: bool = False) -> dict[str, bool]:
159
+ """Skip morphing with attributes and children"""
160
+ return {f"hx-morph-skip{':inherited' if inherited else ''}": True}
161
+
162
+
163
+ def morph_skip_children(*, inherited: bool = False) -> dict[str, bool]:
164
+ """Skip morphing with children only; attributes still morph"""
165
+ return {f"hx-morph-skip-children{':inherited' if inherited else ''}": True}
166
+
167
+
168
+ def target(selector: _selector, *, inherited: bool = False) -> dict[str, str]:
169
+ """Controls where response is inserted.
170
+
171
+ https://four.htmx.org/reference/attributes/hx-target
172
+ """
173
+ return {f"hx-target{':inherited' if inherited else ''}": selector}
174
+
175
+
176
+ def select(selector: _selector, *, inherited: bool = False) -> dict[str, str]:
177
+ """Controls which response part is inserted.
178
+
179
+ https://four.htmx.org/reference/attributes/hx-select
180
+ """
181
+ return {f"hx-select{':inherited' if inherited else ''}": selector}
182
+
183
+
184
+ def on(
185
+ event: _event | _htmx_events = "", js: str = "", *, inherited: bool = False
186
+ ) -> dict[str, str]:
187
+ """Runs inline JavaScript when event fires.
188
+
189
+ https://four.htmx.org/reference/attributes/hx-on
190
+ """
191
+ return {f"hx-on{':' + event if event else ''}{':inherited' if inherited else ''}": js}
192
+
193
+
194
+ class JS:
195
+ """Used to mark strings as js expressions for use in hx-vals and hx-headers."""
196
+
197
+ start = "HXJS-start:"
198
+ end = ":HXJS-end"
199
+
200
+ def __init__(self, value: str) -> None:
201
+ self.value = value
202
+
203
+ def __repr__(self) -> str:
204
+ return f"{JS.start}{self.value}{JS.end}"
205
+
206
+ def __str__(self) -> str:
207
+ return f"JS({self.value})"
208
+
209
+
210
+ class _HTMXJSONEncoder(json.JSONEncoder):
211
+ def default(self, o: Any) -> Any:
212
+ if isinstance(o, JS):
213
+ return repr(o)
214
+ # Let the base class default method raise the TypeError
215
+ return super().default(o)
216
+
217
+ def encode(self, o: Any) -> str:
218
+ string: str = super().encode(o)
219
+ string = string.replace(f'"{JS.start}', "").replace(f'{JS.end}"', "")
220
+ return string
221
+
222
+
223
+ def vals(
224
+ data: dict[str, Any], *, js: bool = False, append: bool = False, inherited: bool = False
225
+ ) -> dict[str, str]:
226
+ """Adds values to request parameters.
227
+
228
+ https://four.htmx.org/reference/attributes/hx-vals
229
+ """
230
+ data_string = json.dumps(data, cls=_HTMXJSONEncoder)
231
+ return {
232
+ f"hx-vals{':append' if append else ''}{':inherited' if inherited else ''}": f"{'js:' if js else ''}{data_string}"
233
+ }
234
+
235
+
236
+ def include(
237
+ selector: _selector, *, append: bool = False, inherited: bool = False
238
+ ) -> dict[str, str]:
239
+ """Includes additional element values in request.
240
+
241
+ https://four.htmx.org/reference/attributes/hx-include
242
+ """
243
+ return {f"hx-include{':append' if append else ''}{':inherited' if inherited else ''}": selector}
244
+
245
+
246
+ def swap_oob(
247
+ swap_style: _swap_style | bool = True, selector: _selector = "", *, inherited: bool = False
248
+ ) -> dict[str, str]:
249
+ """Marks response elements to swap into page by ID.
250
+
251
+ https://four.htmx.org/reference/attributes/hx-swap-oob
252
+ """
253
+ if isinstance(swap_style, bool):
254
+ style: str = str(swap_style).lower()
255
+ else:
256
+ style = str(swap_style)
257
+ return {
258
+ f"hx-swap-oob{':inherited' if inherited else ''}": f"{style}{':' + selector if selector else ''}"
259
+ }
260
+
261
+
262
+ def select_oob(*selector: str, inherited: bool = False) -> dict[str, str]:
263
+ """Picks response elements to swap into page by ID.
264
+
265
+ https://four.htmx.org/reference/attributes/hx-select-oob
266
+ """
267
+ return {f"hx-select-oob{':inherited' if inherited else ''}": ", ".join(selector)}
268
+
269
+
270
+ def push_url(push: bool = True, *, inherited: bool = False) -> dict[str, str]:
271
+ """Pushes URL into browser history.
272
+
273
+ https://four.htmx.org/reference/attributes/hx-push-url
274
+ """
275
+ return {f"hx-push-url{':inherited' if inherited else ''}": str(push).lower()}
276
+
277
+
278
+ def replace_url(replace: bool = True, *, inherited: bool = False) -> dict[str, str]:
279
+ """Replaces current URL in browser history.
280
+
281
+ https://four.htmx.org/reference/attributes/hx-replace-url
282
+ """
283
+ return {f"hx-replace-url{':inherited' if inherited else ''}": str(replace).lower()}
284
+
285
+
286
+ def headers(data: dict[str, Any], *, js: bool = False, inherited: bool = False) -> dict[str, str]:
287
+ """Adds custom headers to request.
288
+
289
+ https://four.htmx.org/reference/attributes/hx-headers
290
+ """
291
+ return {
292
+ f"hx-headers{':inherited' if inherited else ''}": f"{'js:' if js else ''}{json.dumps(data, cls=_HTMXJSONEncoder)}"
293
+ }
294
+
295
+
296
+ def encoding(
297
+ encoding_string: str | Literal["application/x-www-form-urlencoded", "multipart/form-data"],
298
+ *,
299
+ inherited: bool = False,
300
+ ) -> dict[str, str]:
301
+ """Sets request encoding type.
302
+
303
+ https://four.htmx.org/reference/attributes/hx-encoding
304
+ """
305
+ return {f"hx-encoding{':inherited' if inherited else ''}": encoding_string}
306
+
307
+
308
+ def indicator(
309
+ selector: _selector, *, append: bool = False, inherited: bool = False
310
+ ) -> dict[str, str]:
311
+ """Specifies loading indicator element.
312
+
313
+ https://four.htmx.org/reference/attributes/hx-indicator
314
+ """
315
+ return {
316
+ f"hx-indicator{':append' if append else ''}{':inherited' if inherited else ''}": selector
317
+ }
318
+
319
+
320
+ @overload
321
+ def boost(boost: bool, *, inherited: bool = False) -> dict[str, str]:
322
+ pass
323
+
324
+
325
+ @overload
326
+ def boost(
327
+ *,
328
+ swap: _swap_style | str = "",
329
+ target: _selector = "",
330
+ select: _selector = "",
331
+ inherited: bool = False,
332
+ ) -> dict[str, str]:
333
+ pass
334
+
335
+
336
+ def boost(
337
+ boost: bool | None = None,
338
+ *,
339
+ swap: _swap_style | str = "",
340
+ target: _selector = "",
341
+ select: _selector = "",
342
+ inherited: bool = False,
343
+ ) -> dict[str, str]:
344
+ """Converts links and forms to AJAX.
345
+
346
+ https://four.htmx.org/reference/attributes/hx-boost
347
+ """
348
+ text = ""
349
+ if swap:
350
+ text += f"swap:{swap}"
351
+ if target:
352
+ text += f" target:{target}"
353
+ if select:
354
+ text += f" select:{select}"
355
+ if boost is not None and text == "":
356
+ text = str(boost).lower()
357
+ return {f"hx-boost{':inherited' if inherited else ''}": text}
358
+
359
+
360
+ def sync(
361
+ selector: _selector,
362
+ strategy: Literal["drop", "abort", "replace", "queue first", "queue last", "queue all"],
363
+ *,
364
+ inherited: bool = False,
365
+ ) -> dict[str, str]:
366
+ """Synchronizes requests between elements.
367
+
368
+ https://four.htmx.org/reference/attributes/hx-sync
369
+ """
370
+ return {f"hx-sync{':inherited' if inherited else ''}": f"{selector}:{strategy}"}
371
+
372
+
373
+ def confirm(confirmation_prompt: str, *, inherited: bool = False) -> dict[str, str]:
374
+ """Shows confirmation dialog before request.
375
+
376
+ https://four.htmx.org/reference/attributes/hx-confirm
377
+ """
378
+ return {f"hx-confirm{':inherited' if inherited else ''}": confirmation_prompt}
379
+
380
+
381
+ def validate(validate: bool = True, *, inherited: bool = False) -> dict[str, str]:
382
+ """Validates before submitting request.
383
+
384
+ https://four.htmx.org/reference/attributes/hx-validate
385
+ """
386
+ return {f"hx-validate{':inherited' if inherited else ''}": str(validate).lower()}
387
+
388
+
389
+ def disable(*selector: _selector, merge: bool = False, inherited: bool = False) -> dict[str, str]:
390
+ """Disables elements during request.
391
+
392
+ https://four.htmx.org/reference/attributes/hx-disable
393
+ """
394
+ return {
395
+ f"hx-disable{':merge' if merge else ''}{':inherited' if inherited else ''}": ", ".join(
396
+ selector
397
+ )
398
+ }
399
+
400
+
401
+ def ignore(*, inherited: bool = False) -> dict[str, str]:
402
+ """Disables htmx processing for element.
403
+
404
+ https://four.htmx.org/reference/attributes/hx-ignore
405
+ """
406
+ return {f"hx-ignore{':inherited' if inherited else ''}": ""}
407
+
408
+
409
+ def preserve(*, inherited: bool = False) -> dict[str, str]:
410
+ """Preserves element during swaps.
411
+
412
+ https://four.htmx.org/reference/attributes/hx-preserve
413
+ """
414
+ return {f"hx-preserve{':inherited' if inherited else ''}": "true"}
415
+
416
+
417
+ def status(
418
+ status_code: str | int,
419
+ *,
420
+ swap: _swap_style | None = None,
421
+ target: _selector = "",
422
+ select: _selector = "",
423
+ push: bool | str = "",
424
+ replace: bool | str = "",
425
+ transition: bool | None = None,
426
+ inherited: bool = False,
427
+ ) -> dict[str, str]:
428
+ """Handles responses differently by status code.
429
+
430
+ https://four.htmx.org/reference/attributes/hx-status
431
+ """
432
+ text: str = ""
433
+ if swap is not None:
434
+ text += f"swap:{swap}"
435
+ if target:
436
+ text += f" target:{target}"
437
+ if select:
438
+ text += f" select:{select}"
439
+ if push != "":
440
+ text += f" push:{push}"
441
+ if replace != "":
442
+ text += f" replace:{replace}"
443
+ if transition is not None:
444
+ text += f" transition:{transition}"
445
+ return {f"hx-status:{status_code}{':inherited' if inherited else ''}": text}
446
+
447
+
448
+ def action(url: str, *, inherited: bool = False) -> dict[str, str]:
449
+ """Specifies URL to receive request.
450
+
451
+ https://four.htmx.org/reference/attributes/hx-action
452
+ """
453
+ return {f"hx-action{':inherited' if inherited else ''}": url}
454
+
455
+
456
+ def method(
457
+ text: Literal["get", "post", "put", "patch", "delete"], *, inherited: bool = False
458
+ ) -> dict[str, str]:
459
+ """Specifies HTTP method for request.
460
+
461
+ https://four.htmx.org/reference/attributes/hx-method
462
+ """
463
+ return {f"hx-method{':inherited' if inherited else ''}": text}
464
+
465
+
466
+ def config(
467
+ *,
468
+ timeout: int | None = None,
469
+ credentials: str = "",
470
+ cache: str = "",
471
+ redirect: str = "",
472
+ referrer: str = "",
473
+ integrity: str = "",
474
+ validate: bool | None = None,
475
+ append: bool = False,
476
+ inherited: bool = False,
477
+ ) -> dict[str, str]:
478
+ """Configures request behavior.
479
+
480
+ https://four.htmx.org/reference/attributes/hx-config
481
+ """
482
+ data: dict[str, int | str | bool] = {}
483
+ if timeout is not None:
484
+ data["timeout"] = timeout
485
+ if credentials:
486
+ data["credentials"] = credentials
487
+ if cache:
488
+ data["cache"] = cache
489
+ if redirect:
490
+ data["redirect"] = redirect
491
+ if referrer:
492
+ data["referrer"] = referrer
493
+ if integrity:
494
+ data["integrity"] = integrity
495
+ if validate is not None:
496
+ data["validate"] = validate
497
+ return {
498
+ f"hx-config{':append' if append else ''}{':inherited' if inherited else ''}": json.dumps(
499
+ data
500
+ )
501
+ }
502
+
503
+
504
+ def history_elt(*, inherited: bool = False) -> dict[str, str]:
505
+ """Marks element to swap on history restore.
506
+
507
+ https://four.htmx.org/reference/attributes/hx-history-elt
508
+ """
509
+ return {f"hx-history-elt{':inherited' if inherited else ''}": ""}
@@ -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,9 @@
1
+ htpy_htmx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ htpy_htmx/_types.py,sha256=VNFk6T8nDIvQ9QEiEQbGCo-lGvq1wzBUcIIu37d-uMk,2351
3
+ htpy_htmx/ext.py,sha256=ORI6uXsskt5LqWZXW4BYma8fwjKpFi6OjOBThHRvnM8,4351
4
+ htpy_htmx/hx.py,sha256=HaimQdXfOWa73yl5lIcUUoGFksKmRn6rlGd_u1uxgow,14787
5
+ htpy_htmx-0.1.0.dist-info/licenses/LICENCE,sha256=Vl3Hxc8G6RHH3q4lgrNKZuJiVQD3FOdwGbUAkJvN1Jc,1072
6
+ htpy_htmx-0.1.0.dist-info/METADATA,sha256=9NcFHrSbGSDYOSWRUTIi6406UiI_erf70IraWiOd95Y,2571
7
+ htpy_htmx-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
8
+ htpy_htmx-0.1.0.dist-info/top_level.txt,sha256=xKrtpdM7_Rv-NIPYrfAV1qHatgmJuFkuzu4LEHN4Twc,10
9
+ htpy_htmx-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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 @@
1
+ htpy_htmx