htmy 0.7.1__tar.gz → 0.7.3__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.
Potentially problematic release.
This version of htmy might be problematic. Click here for more details.
- {htmy-0.7.1 → htmy-0.7.3}/PKG-INFO +8 -3
- {htmy-0.7.1 → htmy-0.7.3}/README.md +6 -1
- {htmy-0.7.1 → htmy-0.7.3}/htmy/core.py +9 -1
- {htmy-0.7.1 → htmy-0.7.3}/htmy/i18n.py +1 -1
- htmy-0.7.3/htmy/io.py +14 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/snippet.py +16 -4
- {htmy-0.7.1 → htmy-0.7.3}/pyproject.toml +12 -12
- htmy-0.7.1/htmy/io.py +0 -1
- {htmy-0.7.1 → htmy-0.7.3}/LICENSE +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/__init__.py +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/etree.py +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/function_component.py +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/html.py +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/md/__init__.py +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/md/core.py +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/md/typing.py +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/py.typed +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/renderer/__init__.py +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/renderer/baseline.py +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/renderer/default.py +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/typing.py +0 -0
- {htmy-0.7.1 → htmy-0.7.3}/htmy/utils.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: htmy
|
|
3
|
-
Version: 0.7.
|
|
4
|
-
Summary: Async, pure-Python rendering engine.
|
|
3
|
+
Version: 0.7.3
|
|
4
|
+
Summary: Async, pure-Python server-side rendering engine.
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Peter Volf
|
|
7
7
|
Author-email: do.volfp@gmail.com
|
|
@@ -28,7 +28,7 @@ Description-Content-Type: text/markdown
|
|
|
28
28
|
|
|
29
29
|
# `htmy`
|
|
30
30
|
|
|
31
|
-
**Async**, **pure-Python** rendering engine.
|
|
31
|
+
**Async**, **pure-Python** server-side rendering engine.
|
|
32
32
|
|
|
33
33
|
Unleash your creativity with the full power and Python, without the hassle of learning a new templating language or dealing with its limitations!
|
|
34
34
|
|
|
@@ -285,6 +285,7 @@ These are default tag attribute formatting rules:
|
|
|
285
285
|
- `bool` attribute values are converted to strings (`"true"` and `"false"`).
|
|
286
286
|
- `XBool.true` attributes values are converted to an empty string, and `XBool.false` values are skipped (only the attribute name is rendered).
|
|
287
287
|
- `date` and `datetime` attribute values are converted to ISO strings.
|
|
288
|
+
- Complex values such as lists, dictionaries, tuples, and sets are JSON serialized.
|
|
288
289
|
|
|
289
290
|
### Error boundary
|
|
290
291
|
|
|
@@ -308,6 +309,10 @@ FastAPI:
|
|
|
308
309
|
|
|
309
310
|
- [FastHX](https://github.com/volfpeter/fasthx)
|
|
310
311
|
|
|
312
|
+
## External examples
|
|
313
|
+
|
|
314
|
+
- [lipsum-chat](https://github.com/volfpeter/lipsum-chat): A simple chat application using `FastAPI`, `htmx`, and `fasthx`.
|
|
315
|
+
|
|
311
316
|
## Why
|
|
312
317
|
|
|
313
318
|
At one end of the spectrum, there are the complete application frameworks that combine the server (Python) and client (JavaScript) applications with the entire state management and synchronization into a single Python (an in some cases an additional JavaScript) package. Some of the most popular examples are: [Reflex](https://github.com/reflex-dev/reflex), [NiceGUI](https://github.com/zauberzeug/nicegui/), [ReactPy](https://github.com/reactive-python/reactpy), and [FastUI](https://github.com/pydantic/FastUI).
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
# `htmy`
|
|
11
11
|
|
|
12
|
-
**Async**, **pure-Python** rendering engine.
|
|
12
|
+
**Async**, **pure-Python** server-side rendering engine.
|
|
13
13
|
|
|
14
14
|
Unleash your creativity with the full power and Python, without the hassle of learning a new templating language or dealing with its limitations!
|
|
15
15
|
|
|
@@ -266,6 +266,7 @@ These are default tag attribute formatting rules:
|
|
|
266
266
|
- `bool` attribute values are converted to strings (`"true"` and `"false"`).
|
|
267
267
|
- `XBool.true` attributes values are converted to an empty string, and `XBool.false` values are skipped (only the attribute name is rendered).
|
|
268
268
|
- `date` and `datetime` attribute values are converted to ISO strings.
|
|
269
|
+
- Complex values such as lists, dictionaries, tuples, and sets are JSON serialized.
|
|
269
270
|
|
|
270
271
|
### Error boundary
|
|
271
272
|
|
|
@@ -289,6 +290,10 @@ FastAPI:
|
|
|
289
290
|
|
|
290
291
|
- [FastHX](https://github.com/volfpeter/fasthx)
|
|
291
292
|
|
|
293
|
+
## External examples
|
|
294
|
+
|
|
295
|
+
- [lipsum-chat](https://github.com/volfpeter/lipsum-chat): A simple chat application using `FastAPI`, `htmx`, and `fasthx`.
|
|
296
|
+
|
|
292
297
|
## Why
|
|
293
298
|
|
|
294
299
|
At one end of the spectrum, there are the complete application frameworks that combine the server (Python) and client (JavaScript) applications with the entire state management and synchronization into a single Python (an in some cases an additional JavaScript) package. Some of the most popular examples are: [Reflex](https://github.com/reflex-dev/reflex), [NiceGUI](https://github.com/zauberzeug/nicegui/), [ReactPy](https://github.com/reactive-python/reactpy), and [FastUI](https://github.com/pydantic/FastUI).
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
4
|
import enum
|
|
5
|
+
import json
|
|
5
6
|
from collections.abc import Callable, Container
|
|
6
7
|
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
|
|
7
8
|
from xml.sax.saxutils import escape as xml_escape
|
|
@@ -251,6 +252,9 @@ class Formatter(ContextAware):
|
|
|
251
252
|
"""
|
|
252
253
|
The default, context-aware property name and value formatter.
|
|
253
254
|
|
|
255
|
+
The formatter supports both primitive and (many) complex values, such as lists,
|
|
256
|
+
dictionaries, tuples, and sets. Complex values are JSON-serialized by default.
|
|
257
|
+
|
|
254
258
|
Important: the default implementation looks up the formatter for a given value by checking
|
|
255
259
|
its type, but it doesn't do this check with the base classes of the encountered type. For
|
|
256
260
|
example the formatter will know how to format `datetime` object, but it won't know how to
|
|
@@ -258,7 +262,7 @@ class Formatter(ContextAware):
|
|
|
258
262
|
|
|
259
263
|
One reason for this is efficiency: always checking the base classes of every single value is a
|
|
260
264
|
lot of unnecessary calculation. The other reason is customizability: this way you could use
|
|
261
|
-
subclassing for
|
|
265
|
+
subclassing for formatter selection, e.g. with `LocaleDatetime(datetime)`-like classes.
|
|
262
266
|
|
|
263
267
|
Property name and value formatters may raise a `SkipProperty` error if a property should be skipped.
|
|
264
268
|
"""
|
|
@@ -337,6 +341,10 @@ class Formatter(ContextAware):
|
|
|
337
341
|
bool: lambda v: "true" if v else "false",
|
|
338
342
|
date: lambda d: cast(date, d).isoformat(),
|
|
339
343
|
datetime: lambda d: cast(datetime, d).isoformat(),
|
|
344
|
+
dict: lambda v: json.dumps(v),
|
|
345
|
+
list: lambda v: json.dumps(v),
|
|
346
|
+
tuple: lambda v: json.dumps(v),
|
|
347
|
+
set: lambda v: json.dumps(tuple(v)),
|
|
340
348
|
XBool: lambda v: cast(XBool, v).format(),
|
|
341
349
|
type(None): SkipProperty.format_property,
|
|
342
350
|
}
|
htmy-0.7.3/htmy/io.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from anyio import open_file as open_file
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def load_text_file(path: str | Path) -> str:
|
|
12
|
+
"""Loads the text content from the given path."""
|
|
13
|
+
async with await open_file(path, "r") as f:
|
|
14
|
+
return await f.read()
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
4
|
from collections.abc import Awaitable, Iterator, Mapping
|
|
3
|
-
from
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from async_lru import alru_cache
|
|
4
8
|
|
|
5
9
|
from .core import SafeStr, Text
|
|
6
|
-
from .io import
|
|
10
|
+
from .io import load_text_file
|
|
7
11
|
from .typing import (
|
|
8
12
|
Component,
|
|
9
13
|
ComponentType,
|
|
@@ -13,6 +17,9 @@ from .typing import (
|
|
|
13
17
|
)
|
|
14
18
|
from .utils import as_component_sequence, as_component_type, is_component_sequence
|
|
15
19
|
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
16
23
|
# -- Components and utilities
|
|
17
24
|
|
|
18
25
|
|
|
@@ -248,8 +255,7 @@ class Snippet:
|
|
|
248
255
|
if isinstance(path_or_text, Text):
|
|
249
256
|
return path_or_text
|
|
250
257
|
else:
|
|
251
|
-
|
|
252
|
-
return await f.read()
|
|
258
|
+
return await Snippet._load_text_file(path_or_text)
|
|
253
259
|
|
|
254
260
|
def _render_text(self, text: str, context: Context) -> Component:
|
|
255
261
|
"""
|
|
@@ -257,3 +263,9 @@ class Snippet:
|
|
|
257
263
|
and returns the corresponding component.
|
|
258
264
|
"""
|
|
259
265
|
return SafeStr(text)
|
|
266
|
+
|
|
267
|
+
@staticmethod
|
|
268
|
+
@alru_cache()
|
|
269
|
+
async def _load_text_file(path: str | Path) -> str:
|
|
270
|
+
"""Async text loader with an LRU cache."""
|
|
271
|
+
return await load_text_file(path)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "htmy"
|
|
3
|
-
version = "0.7.
|
|
4
|
-
description = "Async, pure-Python rendering engine."
|
|
3
|
+
version = "0.7.3"
|
|
4
|
+
description = "Async, pure-Python server-side rendering engine."
|
|
5
5
|
authors = ["Peter Volf <do.volfp@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
7
7
|
readme = "README.md"
|
|
@@ -14,7 +14,7 @@ markdown = "^3.7"
|
|
|
14
14
|
|
|
15
15
|
[tool.poetry.group.dev.dependencies]
|
|
16
16
|
mkdocs-material = "^9.5.39"
|
|
17
|
-
mkdocstrings = {extras = ["python"], version = "^0.26.1"}
|
|
17
|
+
mkdocstrings = { extras = ["python"], version = "^0.26.1" }
|
|
18
18
|
mypy = "^1.15.0"
|
|
19
19
|
poethepoet = "^0.29.0"
|
|
20
20
|
pytest = "^8.3.3"
|
|
@@ -49,17 +49,17 @@ exclude = [
|
|
|
49
49
|
"docs",
|
|
50
50
|
]
|
|
51
51
|
lint.select = [
|
|
52
|
-
"B",
|
|
53
|
-
"C",
|
|
54
|
-
"E",
|
|
55
|
-
"F",
|
|
56
|
-
"I",
|
|
57
|
-
"S",
|
|
58
|
-
"W",
|
|
52
|
+
"B", # flake8-bugbear
|
|
53
|
+
"C", # flake8-comprehensions
|
|
54
|
+
"E", # pycodestyle errors
|
|
55
|
+
"F", # pyflakes
|
|
56
|
+
"I", # isort
|
|
57
|
+
"S", # flake8-bandit - we must ignore these rules in tests
|
|
58
|
+
"W", # pycodestyle warnings
|
|
59
59
|
]
|
|
60
60
|
|
|
61
61
|
[tool.ruff.lint.per-file-ignores]
|
|
62
|
-
"tests/**/*" = ["S101"]
|
|
62
|
+
"tests/**/*" = ["S101"] # S101: use of assert detected
|
|
63
63
|
|
|
64
64
|
[tool.poe.tasks]
|
|
65
65
|
check-format = "ruff format --check ."
|
|
@@ -70,4 +70,4 @@ mypy = "mypy ."
|
|
|
70
70
|
test = "python -m pytest tests"
|
|
71
71
|
static-checks.sequence = ["lint", "check-format", "mypy"]
|
|
72
72
|
static-checks.ignore_fail = "return_non_zero"
|
|
73
|
-
serve-docs = "mkdocs serve"
|
|
73
|
+
serve-docs = "mkdocs serve"
|
htmy-0.7.1/htmy/io.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from anyio import open_file as open_file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|