htmy 0.7.0__tar.gz → 0.9.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.
- {htmy-0.7.0 → htmy-0.9.0}/LICENSE +1 -1
- htmy-0.7.0/README.md → htmy-0.9.0/PKG-INFO +76 -4
- htmy-0.7.0/PKG-INFO → htmy-0.9.0/README.md +52 -24
- {htmy-0.7.0 → htmy-0.9.0}/htmy/__init__.py +5 -0
- {htmy-0.7.0 → htmy-0.9.0}/htmy/core.py +9 -1
- {htmy-0.7.0 → htmy-0.9.0}/htmy/etree.py +25 -11
- {htmy-0.7.0 → htmy-0.9.0}/htmy/html.py +13 -1
- {htmy-0.7.0 → htmy-0.9.0}/htmy/i18n.py +1 -1
- htmy-0.9.0/htmy/io.py +14 -0
- {htmy-0.7.0 → htmy-0.9.0}/htmy/md/core.py +6 -2
- {htmy-0.7.0 → htmy-0.9.0}/htmy/renderer/baseline.py +10 -4
- {htmy-0.7.0 → htmy-0.9.0}/htmy/renderer/default.py +14 -6
- {htmy-0.7.0 → htmy-0.9.0}/htmy/snippet.py +20 -4
- {htmy-0.7.0 → htmy-0.9.0}/htmy/typing.py +25 -1
- htmy-0.9.0/pyproject.toml +87 -0
- htmy-0.7.0/htmy/io.py +0 -1
- htmy-0.7.0/pyproject.toml +0 -73
- {htmy-0.7.0 → htmy-0.9.0}/htmy/function_component.py +0 -0
- {htmy-0.7.0 → htmy-0.9.0}/htmy/md/__init__.py +0 -0
- {htmy-0.7.0 → htmy-0.9.0}/htmy/md/typing.py +0 -0
- {htmy-0.7.0 → htmy-0.9.0}/htmy/py.typed +0 -0
- {htmy-0.7.0 → htmy-0.9.0}/htmy/renderer/__init__.py +0 -0
- {htmy-0.7.0 → htmy-0.9.0}/htmy/utils.py +0 -0
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: htmy
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Async, pure-Python server-side rendering engine.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Peter Volf
|
|
8
|
+
Author-email: do.volfp@gmail.com
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Provides-Extra: lxml
|
|
18
|
+
Requires-Dist: anyio (>=4.6.2.post1,<5.0.0)
|
|
19
|
+
Requires-Dist: async-lru (>=2.0.4,<3.0.0)
|
|
20
|
+
Requires-Dist: lxml (>=6.0.0) ; extra == "lxml"
|
|
21
|
+
Requires-Dist: markdown (>=3.8,<4.0)
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
1
24
|

|
|
2
25
|

|
|
3
26
|

|
|
@@ -9,13 +32,13 @@
|
|
|
9
32
|
|
|
10
33
|
# `htmy`
|
|
11
34
|
|
|
12
|
-
**Async**, **pure-Python** rendering engine.
|
|
35
|
+
**Async**, **pure-Python** server-side rendering engine.
|
|
13
36
|
|
|
14
37
|
Unleash your creativity with the full power and Python, without the hassle of learning a new templating language or dealing with its limitations!
|
|
15
38
|
|
|
16
39
|
## Key features
|
|
17
40
|
|
|
18
|
-
- **Async**-first, to let you make the best use of
|
|
41
|
+
- **Async**-first, to let you make the best use of modern async tools.
|
|
19
42
|
- **Powerful**, React-like **context support**, so you can avoid prop-drilling.
|
|
20
43
|
- Sync and async **function components** with **decorator syntax**.
|
|
21
44
|
- All baseline **HTML** tags built-in.
|
|
@@ -26,8 +49,23 @@ Unleash your creativity with the full power and Python, without the hassle of le
|
|
|
26
49
|
- **Unopinionated**: use the backend, CSS, and JS frameworks of your choice, the way you want to use them.
|
|
27
50
|
- Everything is **easily customizable**, from the rendering engine to components, formatting and context management.
|
|
28
51
|
- Automatic and customizable **property-name conversion** from snake case to kebab case.
|
|
52
|
+
- **Compatible** with any other templating library through wrappers.
|
|
29
53
|
- **Fully-typed**.
|
|
30
54
|
|
|
55
|
+
## Testimonials
|
|
56
|
+
|
|
57
|
+
"Thank you for your work on `fasthx`, as well as `htmy`! I've never had an easier time developing with another stack." ([ref](https://github.com/volfpeter/fasthx/discussions/77))
|
|
58
|
+
|
|
59
|
+
"One of the main parts of the `FastAPI` -> `fasthx` -> `htmy` integration I'm falling in love with is its explicitness, and not too much magic happening." ([ref](https://github.com/volfpeter/fasthx/issues/54))
|
|
60
|
+
|
|
61
|
+
"Thank you for your work on `htmy` and `fasthx`, both have been very pleasant to use, and the APIs are both intuitive and simple. Great work." ([ref](https://github.com/volfpeter/fasthx/issues/54))
|
|
62
|
+
|
|
63
|
+
"I love that the language-embedded HTML generation library approach is becoming more popular." ([ref](https://www.reddit.com/r/programming/comments/1h1a0dx/comment/lzd3phw))
|
|
64
|
+
|
|
65
|
+
"Neat approach and it naturally solves the partial templates problem 👍" ([ref](https://www.reddit.com/r/Python/comments/1gp3mww/comment/lwqj4fc))
|
|
66
|
+
|
|
67
|
+
"Great API design!" ([ref](https://www.reddit.com/r/Python/comments/1gp3mww/comment/lwpdyq9))
|
|
68
|
+
|
|
31
69
|
## Support
|
|
32
70
|
|
|
33
71
|
Consider supporting the development and maintenance of the project through [sponsoring](https://buymeacoffee.com/volfpeter), or reach out for [consulting](https://www.volfp.com/contact?subject=Consulting%20-%20HTMY) so you can get the most out of the library.
|
|
@@ -40,6 +78,10 @@ The package is available on PyPI and can be installed with:
|
|
|
40
78
|
$ pip install htmy
|
|
41
79
|
```
|
|
42
80
|
|
|
81
|
+
The package has the following optional dependencies:
|
|
82
|
+
|
|
83
|
+
- `lxml` *(recommended)*: When installed, it is prioritized over `xml.etree.ElementTree` and provides more secure, faster, and more flexible HTML and XML processing. It is used, for example, for Markdown processing. Install with: `pip install "htmy[lxml]"`.
|
|
84
|
+
|
|
43
85
|
## Concepts
|
|
44
86
|
|
|
45
87
|
The entire library -- from the rendering engine itself to the built-in components -- is built around a few simple protocols and a handful of simple utility classes. This means that you can easily customize, extend, or replace basically everything in the library. Yes, even the rendering engine. The remaining parts will keep working as expected.
|
|
@@ -97,7 +139,7 @@ user_table = html.table(
|
|
|
97
139
|
)
|
|
98
140
|
```
|
|
99
141
|
|
|
100
|
-
`htmy` also provides a `@component` decorator that can be used on sync or async `my_component(props: MyProps, context: Context) -> Component` functions to convert them into components (preserving the `props` typing).
|
|
142
|
+
`htmy` also provides a powerful `@component` decorator that can be used on sync or async `my_component(props: MyProps, context: Context) -> Component` functions and methods to convert them into components (preserving the `props` typing). You can find out more about this feature in the [Function components](https://volfpeter.github.io/htmy/function-components/) guide.
|
|
101
143
|
|
|
102
144
|
Here is the same example as above, but with function components:
|
|
103
145
|
|
|
@@ -266,6 +308,7 @@ These are default tag attribute formatting rules:
|
|
|
266
308
|
- `bool` attribute values are converted to strings (`"true"` and `"false"`).
|
|
267
309
|
- `XBool.true` attributes values are converted to an empty string, and `XBool.false` values are skipped (only the attribute name is rendered).
|
|
268
310
|
- `date` and `datetime` attribute values are converted to ISO strings.
|
|
311
|
+
- Complex values such as lists, dictionaries, tuples, and sets are JSON serialized.
|
|
269
312
|
|
|
270
313
|
### Error boundary
|
|
271
314
|
|
|
@@ -283,11 +326,39 @@ If a component executes a potentially "long-running" synchronous call, it is str
|
|
|
283
326
|
|
|
284
327
|
In all other cases, it's best to use sync components.
|
|
285
328
|
|
|
329
|
+
## XSS prevention
|
|
330
|
+
|
|
331
|
+
`htmy` does XML/HTML escaping by default. This means user input is normally sanitized and rendered safely.
|
|
332
|
+
|
|
333
|
+
There are a couple of notable exceptions to this, where components by design allow XML/HTML inputs and assume they are safe:
|
|
334
|
+
|
|
335
|
+
- `Snippet`: The primary use-case is to efficiently render XML/HTML templates, filling in placeholders with dynamic content. In this case you must ensure that the input template itself is safe!
|
|
336
|
+
- `MD`: This component builds on `Snippet` to support markdown inputs and performs automatic markdown to HTML conversion. You must ensure the input text is safe!
|
|
337
|
+
|
|
338
|
+
## AI assistance
|
|
339
|
+
|
|
340
|
+
The library is registered at [Context7](https://context7.com/volfpeter).
|
|
341
|
+
|
|
342
|
+
To get good AI assistance, all you need to do is register the Context7 MCP server in your coding tool and tell the agent to use it.
|
|
343
|
+
|
|
344
|
+
Because of the similarity with native HTML, JSX, and React, you can expect good results, both for vibe coding or inline completion.
|
|
345
|
+
|
|
346
|
+
## Compatibility and performance
|
|
347
|
+
|
|
348
|
+
By design, `htmy` is compatible with any other Python templating library, for example Jinja, through wrappers. A wrapper is simply a custom `htmy` component that internally offloads rendering to another templating framework. This makes it possible to easily combine `htmy` with other libraries, to gradually adopt it, and even to enjoy the benefits of multiple frameworks.
|
|
349
|
+
|
|
350
|
+
Performance strongly depends on how you use `htmy`. The `Snippet` component for example makes it possible to reach almost Python string formatting performance, while rendering large, deep component trees is noticeably slower than Jinja for example. Wrapping another templating library for certain use-cases, or pre-rendering components and later using `Snippet` to fill in the dynamic content can be beneficial for performance.
|
|
351
|
+
|
|
286
352
|
## Framework integrations
|
|
287
353
|
|
|
288
354
|
FastAPI:
|
|
289
355
|
|
|
290
|
-
- [
|
|
356
|
+
- [holm](https://github.com/volfpeter/holm): Web development framework that brings the Next.js developer experience to Python, built on FastAPI, htmy, and FastHX.
|
|
357
|
+
- [FastHX](https://github.com/volfpeter/fasthx): Declarative server-side rendering utility for FastAPI with built-in HTMX support.
|
|
358
|
+
|
|
359
|
+
## External examples
|
|
360
|
+
|
|
361
|
+
- [lipsum-chat](https://github.com/volfpeter/lipsum-chat): A simple chat application using `FastAPI`, `htmx`, and `fasthx`.
|
|
291
362
|
|
|
292
363
|
## Why
|
|
293
364
|
|
|
@@ -328,3 +399,4 @@ We welcome contributions from the community to help improve the project! Whether
|
|
|
328
399
|
## License - MIT
|
|
329
400
|
|
|
330
401
|
The package is open-sourced under the conditions of the [MIT license](https://choosealicense.com/licenses/mit/).
|
|
402
|
+
|
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: htmy
|
|
3
|
-
Version: 0.7.0
|
|
4
|
-
Summary: Async, pure-Python rendering engine.
|
|
5
|
-
License: MIT
|
|
6
|
-
Author: Peter Volf
|
|
7
|
-
Author-email: do.volfp@gmail.com
|
|
8
|
-
Requires-Python: >=3.10,<4.0
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
-
Requires-Dist: anyio (>=4.6.2.post1,<5.0.0)
|
|
16
|
-
Requires-Dist: async-lru (>=2.0.4,<3.0.0)
|
|
17
|
-
Requires-Dist: markdown (>=3.7,<4.0)
|
|
18
|
-
Description-Content-Type: text/markdown
|
|
19
|
-
|
|
20
1
|

|
|
21
2
|

|
|
22
3
|

|
|
@@ -28,13 +9,13 @@ Description-Content-Type: text/markdown
|
|
|
28
9
|
|
|
29
10
|
# `htmy`
|
|
30
11
|
|
|
31
|
-
**Async**, **pure-Python** rendering engine.
|
|
12
|
+
**Async**, **pure-Python** server-side rendering engine.
|
|
32
13
|
|
|
33
14
|
Unleash your creativity with the full power and Python, without the hassle of learning a new templating language or dealing with its limitations!
|
|
34
15
|
|
|
35
16
|
## Key features
|
|
36
17
|
|
|
37
|
-
- **Async**-first, to let you make the best use of
|
|
18
|
+
- **Async**-first, to let you make the best use of modern async tools.
|
|
38
19
|
- **Powerful**, React-like **context support**, so you can avoid prop-drilling.
|
|
39
20
|
- Sync and async **function components** with **decorator syntax**.
|
|
40
21
|
- All baseline **HTML** tags built-in.
|
|
@@ -45,8 +26,23 @@ Unleash your creativity with the full power and Python, without the hassle of le
|
|
|
45
26
|
- **Unopinionated**: use the backend, CSS, and JS frameworks of your choice, the way you want to use them.
|
|
46
27
|
- Everything is **easily customizable**, from the rendering engine to components, formatting and context management.
|
|
47
28
|
- Automatic and customizable **property-name conversion** from snake case to kebab case.
|
|
29
|
+
- **Compatible** with any other templating library through wrappers.
|
|
48
30
|
- **Fully-typed**.
|
|
49
31
|
|
|
32
|
+
## Testimonials
|
|
33
|
+
|
|
34
|
+
"Thank you for your work on `fasthx`, as well as `htmy`! I've never had an easier time developing with another stack." ([ref](https://github.com/volfpeter/fasthx/discussions/77))
|
|
35
|
+
|
|
36
|
+
"One of the main parts of the `FastAPI` -> `fasthx` -> `htmy` integration I'm falling in love with is its explicitness, and not too much magic happening." ([ref](https://github.com/volfpeter/fasthx/issues/54))
|
|
37
|
+
|
|
38
|
+
"Thank you for your work on `htmy` and `fasthx`, both have been very pleasant to use, and the APIs are both intuitive and simple. Great work." ([ref](https://github.com/volfpeter/fasthx/issues/54))
|
|
39
|
+
|
|
40
|
+
"I love that the language-embedded HTML generation library approach is becoming more popular." ([ref](https://www.reddit.com/r/programming/comments/1h1a0dx/comment/lzd3phw))
|
|
41
|
+
|
|
42
|
+
"Neat approach and it naturally solves the partial templates problem 👍" ([ref](https://www.reddit.com/r/Python/comments/1gp3mww/comment/lwqj4fc))
|
|
43
|
+
|
|
44
|
+
"Great API design!" ([ref](https://www.reddit.com/r/Python/comments/1gp3mww/comment/lwpdyq9))
|
|
45
|
+
|
|
50
46
|
## Support
|
|
51
47
|
|
|
52
48
|
Consider supporting the development and maintenance of the project through [sponsoring](https://buymeacoffee.com/volfpeter), or reach out for [consulting](https://www.volfp.com/contact?subject=Consulting%20-%20HTMY) so you can get the most out of the library.
|
|
@@ -59,6 +55,10 @@ The package is available on PyPI and can be installed with:
|
|
|
59
55
|
$ pip install htmy
|
|
60
56
|
```
|
|
61
57
|
|
|
58
|
+
The package has the following optional dependencies:
|
|
59
|
+
|
|
60
|
+
- `lxml` *(recommended)*: When installed, it is prioritized over `xml.etree.ElementTree` and provides more secure, faster, and more flexible HTML and XML processing. It is used, for example, for Markdown processing. Install with: `pip install "htmy[lxml]"`.
|
|
61
|
+
|
|
62
62
|
## Concepts
|
|
63
63
|
|
|
64
64
|
The entire library -- from the rendering engine itself to the built-in components -- is built around a few simple protocols and a handful of simple utility classes. This means that you can easily customize, extend, or replace basically everything in the library. Yes, even the rendering engine. The remaining parts will keep working as expected.
|
|
@@ -116,7 +116,7 @@ user_table = html.table(
|
|
|
116
116
|
)
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
`htmy` also provides a `@component` decorator that can be used on sync or async `my_component(props: MyProps, context: Context) -> Component` functions to convert them into components (preserving the `props` typing).
|
|
119
|
+
`htmy` also provides a powerful `@component` decorator that can be used on sync or async `my_component(props: MyProps, context: Context) -> Component` functions and methods to convert them into components (preserving the `props` typing). You can find out more about this feature in the [Function components](https://volfpeter.github.io/htmy/function-components/) guide.
|
|
120
120
|
|
|
121
121
|
Here is the same example as above, but with function components:
|
|
122
122
|
|
|
@@ -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
|
|
|
@@ -302,11 +303,39 @@ If a component executes a potentially "long-running" synchronous call, it is str
|
|
|
302
303
|
|
|
303
304
|
In all other cases, it's best to use sync components.
|
|
304
305
|
|
|
306
|
+
## XSS prevention
|
|
307
|
+
|
|
308
|
+
`htmy` does XML/HTML escaping by default. This means user input is normally sanitized and rendered safely.
|
|
309
|
+
|
|
310
|
+
There are a couple of notable exceptions to this, where components by design allow XML/HTML inputs and assume they are safe:
|
|
311
|
+
|
|
312
|
+
- `Snippet`: The primary use-case is to efficiently render XML/HTML templates, filling in placeholders with dynamic content. In this case you must ensure that the input template itself is safe!
|
|
313
|
+
- `MD`: This component builds on `Snippet` to support markdown inputs and performs automatic markdown to HTML conversion. You must ensure the input text is safe!
|
|
314
|
+
|
|
315
|
+
## AI assistance
|
|
316
|
+
|
|
317
|
+
The library is registered at [Context7](https://context7.com/volfpeter).
|
|
318
|
+
|
|
319
|
+
To get good AI assistance, all you need to do is register the Context7 MCP server in your coding tool and tell the agent to use it.
|
|
320
|
+
|
|
321
|
+
Because of the similarity with native HTML, JSX, and React, you can expect good results, both for vibe coding or inline completion.
|
|
322
|
+
|
|
323
|
+
## Compatibility and performance
|
|
324
|
+
|
|
325
|
+
By design, `htmy` is compatible with any other Python templating library, for example Jinja, through wrappers. A wrapper is simply a custom `htmy` component that internally offloads rendering to another templating framework. This makes it possible to easily combine `htmy` with other libraries, to gradually adopt it, and even to enjoy the benefits of multiple frameworks.
|
|
326
|
+
|
|
327
|
+
Performance strongly depends on how you use `htmy`. The `Snippet` component for example makes it possible to reach almost Python string formatting performance, while rendering large, deep component trees is noticeably slower than Jinja for example. Wrapping another templating library for certain use-cases, or pre-rendering components and later using `Snippet` to fill in the dynamic content can be beneficial for performance.
|
|
328
|
+
|
|
305
329
|
## Framework integrations
|
|
306
330
|
|
|
307
331
|
FastAPI:
|
|
308
332
|
|
|
309
|
-
- [
|
|
333
|
+
- [holm](https://github.com/volfpeter/holm): Web development framework that brings the Next.js developer experience to Python, built on FastAPI, htmy, and FastHX.
|
|
334
|
+
- [FastHX](https://github.com/volfpeter/fasthx): Declarative server-side rendering utility for FastAPI with built-in HTMX support.
|
|
335
|
+
|
|
336
|
+
## External examples
|
|
337
|
+
|
|
338
|
+
- [lipsum-chat](https://github.com/volfpeter/lipsum-chat): A simple chat application using `FastAPI`, `htmx`, and `fasthx`.
|
|
310
339
|
|
|
311
340
|
## Why
|
|
312
341
|
|
|
@@ -347,4 +376,3 @@ We welcome contributions from the community to help improve the project! Whether
|
|
|
347
376
|
## License - MIT
|
|
348
377
|
|
|
349
378
|
The package is open-sourced under the conditions of the [MIT license](https://choosealicense.com/licenses/mit/).
|
|
350
|
-
|
|
@@ -30,12 +30,17 @@ from .typing import HTMYComponentType as HTMYComponentType
|
|
|
30
30
|
from .typing import MutableContext as MutableContext
|
|
31
31
|
from .typing import Properties as Properties
|
|
32
32
|
from .typing import PropertyValue as PropertyValue
|
|
33
|
+
from .typing import RendererType as RendererType
|
|
34
|
+
from .typing import StrictComponentType as StrictComponentType
|
|
33
35
|
from .typing import SyncComponent as SyncComponent
|
|
34
36
|
from .typing import SyncContextProvider as SyncContextProvider
|
|
35
37
|
from .utils import as_component_sequence as as_component_sequence
|
|
36
38
|
from .utils import as_component_type as as_component_type
|
|
37
39
|
from .utils import is_component_sequence as is_component_sequence
|
|
40
|
+
from .utils import join
|
|
38
41
|
from .utils import join_components as join_components
|
|
39
42
|
|
|
43
|
+
join_classes = join
|
|
44
|
+
|
|
40
45
|
HTMY = Renderer
|
|
41
46
|
"""Deprecated alias for `Renderer`."""
|
|
@@ -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
|
}
|
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import xml.etree.ElementTree as ET
|
|
4
|
-
from collections.abc import Callable, Generator
|
|
5
3
|
from typing import TYPE_CHECKING, ClassVar
|
|
6
4
|
from xml.sax.saxutils import unescape
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
try:
|
|
7
|
+
from lxml.etree import _Element as Element
|
|
8
|
+
from lxml.etree import tostring as etree_to_string
|
|
9
|
+
from lxml.html import fragment_fromstring as etree_from_string
|
|
10
|
+
except ImportError:
|
|
11
|
+
from xml.etree.ElementTree import Element # type: ignore[assignment]
|
|
12
|
+
from xml.etree.ElementTree import fromstring as etree_from_string # type: ignore[assignment]
|
|
13
|
+
from xml.etree.ElementTree import tostring as etree_to_string # type: ignore[no-redef]
|
|
14
14
|
|
|
15
|
+
from htmy import ComponentType, Properties
|
|
15
16
|
from htmy.core import Fragment, SafeStr, WildcardTag
|
|
16
17
|
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from collections.abc import Callable, Generator, Mapping
|
|
20
|
+
|
|
17
21
|
|
|
18
22
|
class ETreeConverter:
|
|
19
23
|
"""
|
|
20
24
|
Utility for converting XML strings to custom components.
|
|
25
|
+
|
|
26
|
+
By default the converter uses the standard library's `xml.etree.ElementTree`
|
|
27
|
+
module for string to element tree, and element tree to string conversion,
|
|
28
|
+
but if `lxml` is installed, it will be used instead.
|
|
29
|
+
|
|
30
|
+
Installing `lxml` is recommended for better performance and additional features,
|
|
31
|
+
like performance and support for broken HTML fragments. **Important:** `lxml` is
|
|
32
|
+
far more lenient and flexible than the standard library, so having it installed is
|
|
33
|
+
not only a performance boost, but it may also slightly change the element conversion
|
|
34
|
+
behavior in certain edge-cases!
|
|
21
35
|
"""
|
|
22
36
|
|
|
23
37
|
__slots__ = ("_rules",)
|
|
@@ -43,15 +57,15 @@ class ETreeConverter:
|
|
|
43
57
|
return SafeStr(element)
|
|
44
58
|
|
|
45
59
|
element = f"<{self._htmy_fragment}>{element}</{self._htmy_fragment}>"
|
|
46
|
-
return self.convert_element(
|
|
60
|
+
return self.convert_element(etree_from_string(element)) # noqa: S314 # Only use XML strings from a trusted source.
|
|
47
61
|
|
|
48
62
|
def convert_element(self, element: Element) -> ComponentType:
|
|
49
63
|
"""Converts the given `Element` to a component."""
|
|
50
64
|
rules = self._rules
|
|
51
65
|
if len(rules) == 0:
|
|
52
|
-
return SafeStr(
|
|
66
|
+
return SafeStr(etree_to_string(element, encoding="unicode"))
|
|
53
67
|
|
|
54
|
-
tag: str = element.tag
|
|
68
|
+
tag: str = element.tag # type: ignore[assignment]
|
|
55
69
|
component = Fragment if tag == self._htmy_fragment else rules.get(tag)
|
|
56
70
|
children = self._convert_children(element)
|
|
57
71
|
properties = self._convert_properties(element)
|
|
@@ -285,7 +285,7 @@ class hr(TagWithProps):
|
|
|
285
285
|
__slots__ = ()
|
|
286
286
|
|
|
287
287
|
|
|
288
|
-
class iframe(
|
|
288
|
+
class iframe(Tag):
|
|
289
289
|
"""
|
|
290
290
|
`<iframe>` element.
|
|
291
291
|
|
|
@@ -709,6 +709,18 @@ class i(Tag):
|
|
|
709
709
|
tag_config = _DefaultTagConfig.inline_children
|
|
710
710
|
|
|
711
711
|
|
|
712
|
+
class kbd(Tag):
|
|
713
|
+
"""
|
|
714
|
+
`<kbd>` element.
|
|
715
|
+
|
|
716
|
+
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd.
|
|
717
|
+
"""
|
|
718
|
+
|
|
719
|
+
__slots__ = ()
|
|
720
|
+
|
|
721
|
+
tag_config = _DefaultTagConfig.inline_children
|
|
722
|
+
|
|
723
|
+
|
|
712
724
|
class picture(Tag):
|
|
713
725
|
"""
|
|
714
726
|
`<picture>` element.
|
htmy-0.9.0/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()
|
|
@@ -22,7 +22,7 @@ class MarkdownParser(ContextAware):
|
|
|
22
22
|
Context-aware markdown parser.
|
|
23
23
|
|
|
24
24
|
By default, this class uses the `markdown` library with a sensible set of
|
|
25
|
-
[extensions](https://python-markdown.github.io/extensions/) including code
|
|
25
|
+
[extensions](https://python-markdown.github.io/extensions/) including code highlighting.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
28
|
__slots__ = ("_md",)
|
|
@@ -85,10 +85,14 @@ class MD(Snippet):
|
|
|
85
85
|
It supports all the processing utilities of `Snippet`, including `text_resolver` and
|
|
86
86
|
`text_processor` for formatting, token replacement, and slot conversion to components.
|
|
87
87
|
|
|
88
|
-
One note
|
|
88
|
+
One note regarding slot conversion (`text_resolver`): it is executed before markdown parsing,
|
|
89
89
|
and all string segments of the resulting component sequence are parsed individually by the
|
|
90
90
|
markdown parser. As a consequence, you should only use slots in places where the preceding
|
|
91
91
|
and following texts individually result in valid markdown.
|
|
92
|
+
|
|
93
|
+
**Warning:** The component treats its input as trusted. If any part of the input comes from
|
|
94
|
+
untrusted sources, ensure it is safely escaped (using for example `htmy.xml_format_string`)!
|
|
95
|
+
Passing untrusted input to this component leads to XSS vulnerabilities.
|
|
92
96
|
"""
|
|
93
97
|
|
|
94
98
|
__slots__ = (
|
|
@@ -44,6 +44,8 @@ class Renderer:
|
|
|
44
44
|
"""
|
|
45
45
|
Renders the given component.
|
|
46
46
|
|
|
47
|
+
Implements `htmy.typing.RendererType`.
|
|
48
|
+
|
|
47
49
|
Arguments:
|
|
48
50
|
component: The component to render.
|
|
49
51
|
context: An optional rendering context.
|
|
@@ -71,16 +73,18 @@ class Renderer:
|
|
|
71
73
|
"""
|
|
72
74
|
if isinstance(component, str):
|
|
73
75
|
return self._string_formatter(component)
|
|
76
|
+
elif component is None:
|
|
77
|
+
return ""
|
|
74
78
|
elif isinstance(component, Iterable):
|
|
75
79
|
rendered_children = await asyncio.gather(
|
|
76
|
-
*(self._render_one(comp, context) for comp in component)
|
|
80
|
+
*(self._render_one(comp, context) for comp in component if comp is not None)
|
|
77
81
|
)
|
|
78
82
|
|
|
79
|
-
return "".join(rendered_children)
|
|
83
|
+
return "".join(child for child in rendered_children if child is not None)
|
|
80
84
|
else:
|
|
81
|
-
return await self._render_one(component, context)
|
|
85
|
+
return await self._render_one(component, context) or ""
|
|
82
86
|
|
|
83
|
-
async def _render_one(self, component: ComponentType, context: Context) -> str:
|
|
87
|
+
async def _render_one(self, component: ComponentType, context: Context) -> str | None:
|
|
84
88
|
"""
|
|
85
89
|
Renders a single component.
|
|
86
90
|
|
|
@@ -93,6 +97,8 @@ class Renderer:
|
|
|
93
97
|
"""
|
|
94
98
|
if isinstance(component, str):
|
|
95
99
|
return self._string_formatter(component)
|
|
100
|
+
elif component is None:
|
|
101
|
+
return None
|
|
96
102
|
else:
|
|
97
103
|
child_context: Context = context
|
|
98
104
|
if hasattr(component, "htmy_context"): # isinstance() is too expensive.
|
|
@@ -181,7 +181,9 @@ class _ComponentRenderer:
|
|
|
181
181
|
`node.component` must be an `HTMYComponentType` (single component and not `str`).
|
|
182
182
|
"""
|
|
183
183
|
component = node.component
|
|
184
|
-
if
|
|
184
|
+
if component is None:
|
|
185
|
+
pass # Just skip the node
|
|
186
|
+
elif asyncio.iscoroutinefunction(component.htmy): # type: ignore[union-attr]
|
|
185
187
|
self._async_todos.append((node, child_context))
|
|
186
188
|
elif isinstance(component, ErrorBoundary):
|
|
187
189
|
self._error_boundary_todos.append((node, child_context))
|
|
@@ -199,25 +201,29 @@ class _ComponentRenderer:
|
|
|
199
201
|
while sync_todos:
|
|
200
202
|
node, child_context = sync_todos.pop()
|
|
201
203
|
component = node.component
|
|
204
|
+
if component is None:
|
|
205
|
+
continue
|
|
206
|
+
|
|
202
207
|
if hasattr(component, "htmy_context"): # isinstance() is too expensive.
|
|
203
208
|
child_context = await self._extend_context(component, child_context) # type: ignore[arg-type]
|
|
204
209
|
|
|
205
|
-
if asyncio.iscoroutinefunction(
|
|
210
|
+
if asyncio.iscoroutinefunction(component.htmy): # type: ignore[union-attr]
|
|
206
211
|
async_todos.append((node, child_context))
|
|
207
212
|
else:
|
|
208
|
-
result: Component =
|
|
213
|
+
result: Component = component.htmy(child_context) # type: ignore[assignment,union-attr]
|
|
209
214
|
process_node_result(node, result, child_context)
|
|
210
215
|
|
|
211
216
|
if async_todos:
|
|
212
|
-
|
|
213
|
-
async_todos
|
|
217
|
+
current_async_todos = async_todos
|
|
218
|
+
self._async_todos = async_todos = deque()
|
|
219
|
+
await asyncio.gather(*(process_async_node(n, ctx) for n, ctx in current_async_todos))
|
|
214
220
|
|
|
215
221
|
if self._error_boundary_todos:
|
|
216
222
|
await asyncio.gather(
|
|
217
223
|
*(self._process_error_boundary(n, ctx) for n, ctx in self._error_boundary_todos)
|
|
218
224
|
)
|
|
219
225
|
|
|
220
|
-
return "".join(node.component for node in self._root.iter_nodes()) # type: ignore[misc]
|
|
226
|
+
return "".join(node.component for node in self._root.iter_nodes() if node.component is not None) # type: ignore[misc]
|
|
221
227
|
|
|
222
228
|
|
|
223
229
|
async def _render_component(
|
|
@@ -269,6 +275,8 @@ class Renderer:
|
|
|
269
275
|
"""
|
|
270
276
|
Renders the given component.
|
|
271
277
|
|
|
278
|
+
Implements `htmy.typing.RendererType`.
|
|
279
|
+
|
|
272
280
|
Arguments:
|
|
273
281
|
component: The component to render.
|
|
274
282
|
context: An optional rendering context.
|
|
@@ -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
|
|
|
@@ -151,6 +158,10 @@ class Snippet:
|
|
|
151
158
|
"""
|
|
152
159
|
Component that renders text, which may be asynchronously loaded from a file.
|
|
153
160
|
|
|
161
|
+
**Warning:** The component treats its input as trusted. If any part of the input comes from
|
|
162
|
+
untrusted sources, ensure it is safely escaped (using for example `htmy.xml_format_string`)!
|
|
163
|
+
Passing untrusted input to this component leads to XSS vulnerabilities.
|
|
164
|
+
|
|
154
165
|
The entire snippet processing pipeline consists of the following steps:
|
|
155
166
|
|
|
156
167
|
1. The text content is loaded from a file or passed directly as a `Text` instance.
|
|
@@ -248,8 +259,7 @@ class Snippet:
|
|
|
248
259
|
if isinstance(path_or_text, Text):
|
|
249
260
|
return path_or_text
|
|
250
261
|
else:
|
|
251
|
-
|
|
252
|
-
return await f.read()
|
|
262
|
+
return await Snippet._load_text_file(path_or_text)
|
|
253
263
|
|
|
254
264
|
def _render_text(self, text: str, context: Context) -> Component:
|
|
255
265
|
"""
|
|
@@ -257,3 +267,9 @@ class Snippet:
|
|
|
257
267
|
and returns the corresponding component.
|
|
258
268
|
"""
|
|
259
269
|
return SafeStr(text)
|
|
270
|
+
|
|
271
|
+
@staticmethod
|
|
272
|
+
@alru_cache()
|
|
273
|
+
async def _load_text_file(path: str | Path) -> str:
|
|
274
|
+
"""Async text loader with an LRU cache."""
|
|
275
|
+
return await load_text_file(path)
|
|
@@ -55,7 +55,10 @@ class AsyncComponent(Protocol):
|
|
|
55
55
|
HTMYComponentType: TypeAlias = SyncComponent | AsyncComponent
|
|
56
56
|
"""Sync or async `htmy` component type."""
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
StrictComponentType: TypeAlias = HTMYComponentType | str
|
|
59
|
+
"""Type definition for a single component that's not `None`."""
|
|
60
|
+
|
|
61
|
+
ComponentType: TypeAlias = StrictComponentType | None
|
|
59
62
|
"""Type definition for a single component."""
|
|
60
63
|
|
|
61
64
|
# Omit strings from this type to simplify checks.
|
|
@@ -65,6 +68,27 @@ ComponentSequence: TypeAlias = list[ComponentType] | tuple[ComponentType, ...]
|
|
|
65
68
|
Component: TypeAlias = ComponentType | ComponentSequence
|
|
66
69
|
"""Component type: a single component or a sequence of components."""
|
|
67
70
|
|
|
71
|
+
|
|
72
|
+
# -- Renderer
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class RendererType(Protocol):
|
|
76
|
+
"""Protocol definition for `htmy` renderers."""
|
|
77
|
+
|
|
78
|
+
async def render(self, component: Component, context: Context | None = None) -> str:
|
|
79
|
+
"""
|
|
80
|
+
Renders the given component.
|
|
81
|
+
|
|
82
|
+
Arguments:
|
|
83
|
+
component: The component to render.
|
|
84
|
+
context: An optional rendering context.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
The rendered string.
|
|
88
|
+
"""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
|
|
68
92
|
# -- Context providers
|
|
69
93
|
|
|
70
94
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "htmy"
|
|
3
|
+
version = "0.9.0"
|
|
4
|
+
description = "Async, pure-Python server-side rendering engine."
|
|
5
|
+
authors = ["Peter Volf <do.volfp@gmail.com>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = "^3.10"
|
|
11
|
+
anyio = "^4.6.2.post1"
|
|
12
|
+
async-lru = "^2.0.4"
|
|
13
|
+
markdown = "^3.8"
|
|
14
|
+
lxml = { version = ">=6.0.0", optional = true }
|
|
15
|
+
|
|
16
|
+
[tool.poetry.extras]
|
|
17
|
+
lxml = ["lxml"]
|
|
18
|
+
|
|
19
|
+
[tool.poetry.group.dev.dependencies]
|
|
20
|
+
fastapi = "^0.116.0"
|
|
21
|
+
fasthx = "^2.3.3"
|
|
22
|
+
mkdocs-material = "^9.6.16"
|
|
23
|
+
mkdocstrings = { extras = ["python"], version = "^0.30.0" }
|
|
24
|
+
mypy = "^1.18.2"
|
|
25
|
+
poethepoet = "^0.37.0"
|
|
26
|
+
pytest = "^8.3.3"
|
|
27
|
+
pytest-asyncio = "^0.24.0"
|
|
28
|
+
pytest-random-order = "^1.1.1"
|
|
29
|
+
ruff = "^0.14.4"
|
|
30
|
+
types-markdown = "^3.8.0.20250809"
|
|
31
|
+
typing-extensions = "^4.12.2"
|
|
32
|
+
types-lxml = "^2025.3.30"
|
|
33
|
+
|
|
34
|
+
[build-system]
|
|
35
|
+
requires = ["poetry-core"]
|
|
36
|
+
build-backend = "poetry.core.masonry.api"
|
|
37
|
+
|
|
38
|
+
[tool.mypy]
|
|
39
|
+
strict = true
|
|
40
|
+
show_error_codes = true
|
|
41
|
+
|
|
42
|
+
[tool.pyright]
|
|
43
|
+
venvPath = "."
|
|
44
|
+
venv = ".venv"
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
addopts = "--random-order"
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
line-length = 108
|
|
51
|
+
exclude = [
|
|
52
|
+
".git",
|
|
53
|
+
".mypy_cache",
|
|
54
|
+
".pytest_cache",
|
|
55
|
+
".ruff_cache",
|
|
56
|
+
".venv",
|
|
57
|
+
"dist",
|
|
58
|
+
"docs",
|
|
59
|
+
]
|
|
60
|
+
lint.select = [
|
|
61
|
+
"B", # flake8-bugbear
|
|
62
|
+
"C", # flake8-comprehensions
|
|
63
|
+
"E", # pycodestyle errors
|
|
64
|
+
"F", # pyflakes
|
|
65
|
+
"I", # isort
|
|
66
|
+
"S", # flake8-bandit - we must ignore these rules in tests
|
|
67
|
+
"W", # pycodestyle warnings
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint.per-file-ignores]
|
|
71
|
+
"tests/**/*" = ["S101"] # S101: use of assert detected
|
|
72
|
+
|
|
73
|
+
[tool.poe.tasks]
|
|
74
|
+
format = "ruff format --check ."
|
|
75
|
+
format-fix = "ruff format ."
|
|
76
|
+
|
|
77
|
+
lint = "ruff check ."
|
|
78
|
+
lint-fix = "ruff . --fix"
|
|
79
|
+
|
|
80
|
+
mypy = "mypy ."
|
|
81
|
+
|
|
82
|
+
check.sequence = ["format", "lint", "mypy"]
|
|
83
|
+
check.ignore_fail = "return_non_zero"
|
|
84
|
+
|
|
85
|
+
test = "python -m pytest tests"
|
|
86
|
+
|
|
87
|
+
serve-docs = "mkdocs serve"
|
htmy-0.7.0/htmy/io.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from anyio import open_file as open_file
|
htmy-0.7.0/pyproject.toml
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "htmy"
|
|
3
|
-
version = "0.7.0"
|
|
4
|
-
description = "Async, pure-Python rendering engine."
|
|
5
|
-
authors = ["Peter Volf <do.volfp@gmail.com>"]
|
|
6
|
-
license = "MIT"
|
|
7
|
-
readme = "README.md"
|
|
8
|
-
|
|
9
|
-
[tool.poetry.dependencies]
|
|
10
|
-
python = "^3.10"
|
|
11
|
-
anyio = "^4.6.2.post1"
|
|
12
|
-
async-lru = "^2.0.4"
|
|
13
|
-
markdown = "^3.7"
|
|
14
|
-
|
|
15
|
-
[tool.poetry.group.dev.dependencies]
|
|
16
|
-
mkdocs-material = "^9.5.39"
|
|
17
|
-
mkdocstrings = {extras = ["python"], version = "^0.26.1"}
|
|
18
|
-
mypy = "^1.15.0"
|
|
19
|
-
poethepoet = "^0.29.0"
|
|
20
|
-
pytest = "^8.3.3"
|
|
21
|
-
pytest-asyncio = "^0.24.0"
|
|
22
|
-
pytest-random-order = "^1.1.1"
|
|
23
|
-
ruff = "^0.9.0"
|
|
24
|
-
types-markdown = "^3.7.0.20240822"
|
|
25
|
-
typing-extensions = "^4.12.2"
|
|
26
|
-
fastapi = "^0.115.8"
|
|
27
|
-
fasthx = "^2.2.1"
|
|
28
|
-
|
|
29
|
-
[build-system]
|
|
30
|
-
requires = ["poetry-core"]
|
|
31
|
-
build-backend = "poetry.core.masonry.api"
|
|
32
|
-
|
|
33
|
-
[tool.mypy]
|
|
34
|
-
strict = true
|
|
35
|
-
show_error_codes = true
|
|
36
|
-
|
|
37
|
-
[tool.pytest.ini_options]
|
|
38
|
-
addopts = "--random-order"
|
|
39
|
-
|
|
40
|
-
[tool.ruff]
|
|
41
|
-
line-length = 108
|
|
42
|
-
exclude = [
|
|
43
|
-
".git",
|
|
44
|
-
".mypy_cache",
|
|
45
|
-
".pytest_cache",
|
|
46
|
-
".ruff_cache",
|
|
47
|
-
".venv",
|
|
48
|
-
"dist",
|
|
49
|
-
"docs",
|
|
50
|
-
]
|
|
51
|
-
lint.select = [
|
|
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
|
-
]
|
|
60
|
-
|
|
61
|
-
[tool.ruff.lint.per-file-ignores]
|
|
62
|
-
"tests/**/*" = ["S101"] # S101: use of assert detected
|
|
63
|
-
|
|
64
|
-
[tool.poe.tasks]
|
|
65
|
-
check-format = "ruff format --check ."
|
|
66
|
-
format = "ruff format ."
|
|
67
|
-
lint = "ruff check ."
|
|
68
|
-
lint-fix = "ruff . --fix"
|
|
69
|
-
mypy = "mypy ."
|
|
70
|
-
test = "python -m pytest tests"
|
|
71
|
-
static-checks.sequence = ["lint", "check-format", "mypy"]
|
|
72
|
-
static-checks.ignore_fail = "return_non_zero"
|
|
73
|
-
serve-docs = "mkdocs serve"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|