htmy 0.5.0__py3-none-any.whl → 0.7.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.
htmy/__init__.py CHANGED
@@ -12,14 +12,13 @@ from .core import Text as Text
12
12
  from .core import WildcardTag as WildcardTag
13
13
  from .core import WithContext as WithContext
14
14
  from .core import XBool as XBool
15
- from .core import component as component
16
15
  from .core import xml_format_string as xml_format_string
16
+ from .function_component import component as component
17
17
  from .renderer import Renderer as Renderer
18
18
  from .snippet import Slots as Slots
19
19
  from .snippet import Snippet as Snippet
20
20
  from .typing import AsyncComponent as AsyncComponent
21
21
  from .typing import AsyncContextProvider as AsyncContextProvider
22
- from .typing import AsyncFunctionComponent as AsyncFunctionComponent
23
22
  from .typing import Component as Component
24
23
  from .typing import ComponentSequence as ComponentSequence
25
24
  from .typing import ComponentType as ComponentType
@@ -27,14 +26,12 @@ from .typing import Context as Context
27
26
  from .typing import ContextKey as ContextKey
28
27
  from .typing import ContextProvider as ContextProvider
29
28
  from .typing import ContextValue as ContextValue
30
- from .typing import FunctionComponent as FunctionComponent
31
29
  from .typing import HTMYComponentType as HTMYComponentType
32
30
  from .typing import MutableContext as MutableContext
33
31
  from .typing import Properties as Properties
34
32
  from .typing import PropertyValue as PropertyValue
35
33
  from .typing import SyncComponent as SyncComponent
36
34
  from .typing import SyncContextProvider as SyncContextProvider
37
- from .typing import SyncFunctionComponent as SyncFunctionComponent
38
35
  from .utils import as_component_sequence as as_component_sequence
39
36
  from .utils import as_component_type as as_component_type
40
37
  from .utils import is_component_sequence as is_component_sequence
htmy/core.py CHANGED
@@ -1,25 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import abc
4
- import asyncio
5
4
  import enum
6
5
  from collections.abc import Callable, Container
7
- from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypedDict, cast, overload
6
+ from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
8
7
  from xml.sax.saxutils import escape as xml_escape
9
8
  from xml.sax.saxutils import quoteattr as xml_quoteattr
10
9
 
11
- from .typing import (
12
- AsyncFunctionComponent,
13
- Component,
14
- ComponentType,
15
- Context,
16
- ContextKey,
17
- ContextValue,
18
- FunctionComponent,
19
- PropertyValue,
20
- SyncFunctionComponent,
21
- T,
22
- )
10
+ from .typing import Component, ComponentType, Context, ContextKey, ContextValue, PropertyValue, T
23
11
  from .utils import as_component_type, join_components
24
12
 
25
13
  if TYPE_CHECKING:
@@ -201,85 +189,6 @@ class ContextAware:
201
189
  raise TypeError(f"Invalid context data type for {cls.__name__}.")
202
190
 
203
191
 
204
- # -- Function components
205
-
206
-
207
- class SyncFunctionComponentWrapper(Generic[T]):
208
- """Base class `FunctionComponent` wrappers."""
209
-
210
- __slots__ = ("_props",)
211
-
212
- _wrapped_function: SyncFunctionComponent[T]
213
-
214
- def __init__(self, props: T) -> None:
215
- self._props = props
216
-
217
- def __init_subclass__(cls, *, func: SyncFunctionComponent[T]) -> None:
218
- cls._wrapped_function = func
219
-
220
- def htmy(self, context: Context) -> Component:
221
- """Renders the component."""
222
- # type(self) is necessary, otherwise the wrapped function would be called
223
- # with an extra self argument...
224
- return type(self)._wrapped_function(self._props, context)
225
-
226
-
227
- class AsyncFunctionComponentWrapper(Generic[T]):
228
- """Base class `FunctionComponent` wrappers."""
229
-
230
- __slots__ = ("_props",)
231
-
232
- _wrapped_function: AsyncFunctionComponent[T]
233
-
234
- def __init__(self, props: T) -> None:
235
- self._props = props
236
-
237
- def __init_subclass__(cls, *, func: AsyncFunctionComponent[T]) -> None:
238
- cls._wrapped_function = func
239
-
240
- async def htmy(self, context: Context) -> Component:
241
- """Renders the component."""
242
- # type(self) is necessary, otherwise the wrapped function would be called
243
- # with an extra self argument...
244
- return await type(self)._wrapped_function(self._props, context)
245
-
246
-
247
- @overload
248
- def component(func: SyncFunctionComponent[T]) -> type[SyncFunctionComponentWrapper[T]]: ...
249
-
250
-
251
- @overload
252
- def component(func: AsyncFunctionComponent[T]) -> type[AsyncFunctionComponentWrapper[T]]: ...
253
-
254
-
255
- def component(
256
- func: FunctionComponent[T],
257
- ) -> type[SyncFunctionComponentWrapper[T]] | type[AsyncFunctionComponentWrapper[T]]:
258
- """
259
- Decorator that converts the given function into a component.
260
-
261
- Internally this is achieved by wrapping the function in a pre-configured
262
- `FunctionComponentWrapper` subclass.
263
-
264
- Arguments:
265
- func: The decorated function component.
266
-
267
- Returns:
268
- A pre-configured `FunctionComponentWrapper` subclass.
269
- """
270
-
271
- if asyncio.iscoroutinefunction(func):
272
-
273
- class AsyncFCW(AsyncFunctionComponentWrapper[T], func=func): ...
274
-
275
- return AsyncFCW
276
- else:
277
-
278
- class SyncFCW(SyncFunctionComponentWrapper[T], func=func): ... # type: ignore[arg-type]
279
-
280
- return SyncFCW
281
-
282
-
283
192
  # -- Formatting
284
193
 
285
194
 
@@ -0,0 +1,367 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import Callable, Coroutine
5
+ from typing import Any, Protocol, TypeAlias, overload
6
+
7
+ from .typing import AsyncComponent, Component, Context, SyncComponent
8
+ from .typing import T as TProps
9
+ from .typing import U as TSelf
10
+
11
+ # -- Typing for "full" function components and context only method components.
12
+
13
+ _SyncFunctionComponent: TypeAlias = Callable[[TProps, Context], Component]
14
+ """
15
+ Protocol definition for sync function components that have both a properties and a context argument.
16
+ """
17
+
18
+ _AsyncFunctionComponent: TypeAlias = Callable[[TProps, Context], Coroutine[Any, Any, Component]]
19
+ """
20
+ Protocol definition for async function components that have both a properties and a context argument.
21
+ """
22
+
23
+ # -- Typing for context-only function components.
24
+
25
+ _ContextOnlySyncFunctionComponent: TypeAlias = Callable[[Context], Component]
26
+ """
27
+ Protocol definition for sync function components that only have a context argument.
28
+ """
29
+
30
+
31
+ class _DecoratedContextOnlySyncFunctionComponent(SyncComponent, Protocol):
32
+ """
33
+ Protocol definition for sync components that are also callable, and return a sync
34
+ component when called.
35
+ """
36
+
37
+ def __call__(self) -> SyncComponent: ...
38
+
39
+
40
+ _ContextOnlyAsyncFunctionComponent: TypeAlias = Callable[[Context], Coroutine[Any, Any, Component]]
41
+ """
42
+ Protocol definition for async function components that only have a context argument.
43
+ """
44
+
45
+
46
+ class _DecoratedContextOnlyAsyncFunctionComponent(SyncComponent, Protocol):
47
+ """
48
+ Protocol definition for async components that are also callable, and return an async
49
+ component when called.
50
+ """
51
+
52
+ def __call__(self) -> SyncComponent: ...
53
+
54
+
55
+ # -- Typing for "full" method components.
56
+
57
+ _SyncMethodComponent: TypeAlias = Callable[[TSelf, TProps, Context], Component]
58
+ """
59
+ Protocol definition for sync method components that have both a properties and a context argument.
60
+ """
61
+
62
+ _AsyncMethodComponent: TypeAlias = Callable[[TSelf, TProps, Context], Coroutine[Any, Any, Component]]
63
+ """
64
+ Protocol definition for async method components that have both a properties and a context argument.
65
+ """
66
+
67
+
68
+ # -- Component decorators.
69
+
70
+
71
+ class ComponentDecorators:
72
+ """
73
+ Function component decorators.
74
+ """
75
+
76
+ __slots__ = ()
77
+
78
+ # -- Function component decorator.
79
+
80
+ @overload
81
+ def __call__(self, func: _SyncFunctionComponent[TProps]) -> Callable[[TProps], SyncComponent]: ...
82
+
83
+ @overload
84
+ def __call__(self, func: _AsyncFunctionComponent[TProps]) -> Callable[[TProps], AsyncComponent]: ...
85
+
86
+ def __call__(
87
+ self,
88
+ func: _SyncFunctionComponent[TProps] | _AsyncFunctionComponent[TProps],
89
+ ) -> Callable[[TProps], SyncComponent] | Callable[[TProps], AsyncComponent]:
90
+ """
91
+ Decorator that converts the decorated function into one that must be called with
92
+ the function component's properties and returns a component instance.
93
+
94
+ If used on an async function, the resulting component will also be async;
95
+ otherwise it will be sync.
96
+
97
+ Example:
98
+
99
+ ```python
100
+ @component
101
+ def my_component(props: int, context: Context) -> Component:
102
+ return html.p(f"Value: {props}")
103
+
104
+ async def render() -> str:
105
+ return await Renderer().render(
106
+ my_component(42)
107
+ )
108
+ ```
109
+
110
+ Arguments:
111
+ func: The decorated function.
112
+
113
+ Returns:
114
+ A function that must be called with the function component's properties and
115
+ returns a component instance. (Or loosly speaking, an `HTMYComponentType` which
116
+ can be "instantiated" with the function component's properties.)
117
+ """
118
+
119
+ if asyncio.iscoroutinefunction(func):
120
+
121
+ def async_wrapper(props: TProps) -> AsyncComponent:
122
+ # This function must be async, in case the renderer inspects it to decide how to handle it.
123
+ async def component(context: Context) -> Component:
124
+ return await func(props, context) # type: ignore[no-any-return]
125
+
126
+ component.htmy = component # type: ignore[attr-defined]
127
+ return component # type: ignore[return-value]
128
+
129
+ return async_wrapper
130
+ else:
131
+
132
+ def sync_wrapper(props: TProps) -> SyncComponent:
133
+ def component(context: Context) -> Component:
134
+ return func(props, context) # type: ignore[return-value]
135
+
136
+ component.htmy = component # type: ignore[attr-defined]
137
+ return component # type: ignore[return-value]
138
+
139
+ return sync_wrapper
140
+
141
+ @overload
142
+ def function(self, func: _SyncFunctionComponent[TProps]) -> Callable[[TProps], SyncComponent]: ...
143
+
144
+ @overload
145
+ def function(self, func: _AsyncFunctionComponent[TProps]) -> Callable[[TProps], AsyncComponent]: ...
146
+
147
+ def function(
148
+ self,
149
+ func: _SyncFunctionComponent[TProps] | _AsyncFunctionComponent[TProps],
150
+ ) -> Callable[[TProps], SyncComponent] | Callable[[TProps], AsyncComponent]:
151
+ """
152
+ Decorator that converts the decorated function into one that must be called with
153
+ the function component's properties and returns a component instance.
154
+
155
+ If used on an async function, the resulting component will also be async;
156
+ otherwise it will be sync.
157
+
158
+ This function is just an alias for `__call__()`.
159
+
160
+ Example:
161
+
162
+ ```python
163
+ @component.function
164
+ def my_component(props: int, context: Context) -> Component:
165
+ return html.p(f"Value: {props}")
166
+
167
+ async def render() -> str:
168
+ return await Renderer().render(
169
+ my_component(42)
170
+ )
171
+
172
+ Arguments:
173
+ func: The decorated function.
174
+
175
+ Returns:
176
+ A function that must be called with the function component's properties and
177
+ returns a component instance. (Or loosly speaking, an `HTMYComponentType` which
178
+ can be "instantiated" with the function component's properties.)
179
+ """
180
+ return self(func)
181
+
182
+ # -- Context-only function component decorator.
183
+
184
+ @overload
185
+ def context_only(
186
+ self, func: _ContextOnlySyncFunctionComponent
187
+ ) -> _DecoratedContextOnlySyncFunctionComponent: ...
188
+
189
+ @overload
190
+ def context_only(
191
+ self, func: _ContextOnlyAsyncFunctionComponent
192
+ ) -> _DecoratedContextOnlyAsyncFunctionComponent: ...
193
+
194
+ def context_only(
195
+ self,
196
+ func: _ContextOnlySyncFunctionComponent | _ContextOnlyAsyncFunctionComponent,
197
+ ) -> _DecoratedContextOnlySyncFunctionComponent | _DecoratedContextOnlyAsyncFunctionComponent:
198
+ """
199
+ Decorator that converts the decorated function into a component.
200
+
201
+ If used on an async function, the resulting component will also be async;
202
+ otherwise it will be sync.
203
+
204
+ Example:
205
+
206
+ ```python
207
+ @component.context_only
208
+ def my_component(ctx):
209
+ return "Context only function component."
210
+
211
+ async def render() -> str:
212
+ return await Renderer().render(
213
+ my_component()
214
+ )
215
+ ```
216
+
217
+ Arguments:
218
+ func: The decorated function.
219
+
220
+ Returns:
221
+ The created component.
222
+ """
223
+
224
+ def wrapper() -> SyncComponent | AsyncComponent:
225
+ func.htmy = func # type: ignore[union-attr]
226
+ return func # type: ignore[return-value]
227
+
228
+ # This assignment adds support for context-only function components without call signature.
229
+ wrapper.htmy = func # type: ignore[attr-defined]
230
+ return wrapper # type: ignore[return-value]
231
+
232
+ # -- Method component decorator.
233
+
234
+ @overload
235
+ def method(
236
+ self, func: _SyncMethodComponent[TSelf, TProps]
237
+ ) -> Callable[[TSelf, TProps], SyncComponent]: ...
238
+
239
+ @overload
240
+ def method(
241
+ self, func: _AsyncMethodComponent[TSelf, TProps]
242
+ ) -> Callable[[TSelf, TProps], AsyncComponent]: ...
243
+
244
+ def method(
245
+ self,
246
+ func: _SyncMethodComponent[TSelf, TProps] | _AsyncMethodComponent[TSelf, TProps],
247
+ ) -> Callable[[TSelf, TProps], SyncComponent] | Callable[[TSelf, TProps], AsyncComponent]:
248
+ """
249
+ Decorator that converts the decorated method into one that must be called with
250
+ the method component's properties and returns a component instance.
251
+
252
+ If used on an async method, the resulting component will also be async;
253
+ otherwise it will be sync.
254
+
255
+ Example:
256
+
257
+ ```python
258
+ @dataclass
259
+ class MyBusinessObject:
260
+ message: str
261
+
262
+ @component.method
263
+ def paragraph(self, props: int, context: Context) -> Component:
264
+ return html.p(f"{self.message} {props}")
265
+
266
+
267
+ async def render() -> str:
268
+ return await Renderer().render(
269
+ MyBusinessObject("Hi!").paragraph(42)
270
+ )
271
+ ```
272
+
273
+ Arguments:
274
+ func: The decorated method.
275
+
276
+ Returns:
277
+ A method that must be called with the method component's properties and
278
+ returns a component instance. (Or loosly speaking, an `HTMYComponentType` which
279
+ can be "instantiated" with the method component's properties.)
280
+ """
281
+ if asyncio.iscoroutinefunction(func):
282
+
283
+ def async_wrapper(self: TSelf, props: TProps) -> AsyncComponent:
284
+ # This function must be async, in case the renderer inspects it to decide how to handle it.
285
+ async def component(context: Context) -> Component:
286
+ return await func(self, props, context) # type: ignore[no-any-return]
287
+
288
+ component.htmy = component # type: ignore[attr-defined]
289
+ return component # type: ignore[return-value]
290
+
291
+ return async_wrapper
292
+ else:
293
+
294
+ def sync_wrapper(self: TSelf, props: TProps) -> SyncComponent:
295
+ def component(context: Context) -> Component:
296
+ return func(self, props, context) # type: ignore[return-value]
297
+
298
+ component.htmy = component # type: ignore[attr-defined]
299
+ return component # type: ignore[return-value]
300
+
301
+ return sync_wrapper
302
+
303
+ # -- Context-only function component decorator.
304
+
305
+ @overload
306
+ def context_only_method(
307
+ self, func: _SyncFunctionComponent[TSelf]
308
+ ) -> Callable[[TSelf], SyncComponent]: ...
309
+
310
+ @overload
311
+ def context_only_method(
312
+ self, func: _AsyncFunctionComponent[TSelf]
313
+ ) -> Callable[[TSelf], AsyncComponent]: ...
314
+
315
+ def context_only_method(
316
+ self,
317
+ func: _SyncFunctionComponent[TSelf] | _AsyncFunctionComponent[TSelf],
318
+ ) -> Callable[[TSelf], SyncComponent] | Callable[[TSelf], AsyncComponent]:
319
+ """
320
+ Decorator that converts the decorated method into one that must be called
321
+ without any arguments and returns a component instance.
322
+
323
+ If used on an async method, the resulting component will also be async;
324
+ otherwise it will be sync.
325
+
326
+ Example:
327
+
328
+ ```python
329
+ @dataclass
330
+ class MyBusinessObject:
331
+ message: str
332
+
333
+ @component.context_only_method
334
+ def paragraph(self, context: Context) -> Component:
335
+ return html.p(f"{self.message} Goodbye!")
336
+
337
+
338
+ async def render() -> str:
339
+ return await Renderer().render(
340
+ MyBusinessObject("Hello!").paragraph()
341
+ )
342
+ ```
343
+
344
+ Arguments:
345
+ func: The decorated method.
346
+
347
+ Returns:
348
+ A method that must be called without any arguments and returns a component instance.
349
+ (Or loosly speaking, an `HTMYComponentType` which can be "instantiated" by calling
350
+ the method.)
351
+ """
352
+ # A context only method component must be implemented in the same way as
353
+ # a function component. The self argument replaces the props argument
354
+ # and it is added automatically by Python when the method is called.
355
+ # Even the type hint must be the same.
356
+ # This implementation doesn't make the function itself a component though,
357
+ # so the call signature is always necessary (unlike for context-only function
358
+ # components).
359
+ return self(func)
360
+
361
+
362
+ component = ComponentDecorators()
363
+ """
364
+ Decorators for converting functions into components
365
+
366
+ This is an instance of `ComponentDecorators`.
367
+ """
htmy/typing.py CHANGED
@@ -65,16 +65,6 @@ ComponentSequence: TypeAlias = list[ComponentType] | tuple[ComponentType, ...]
65
65
  Component: TypeAlias = ComponentType | ComponentSequence
66
66
  """Component type: a single component or a sequence of components."""
67
67
 
68
-
69
- SyncFunctionComponent: TypeAlias = Callable[[T, Context], Component]
70
- """Protocol definition for sync function components."""
71
-
72
- AsyncFunctionComponent: TypeAlias = Callable[[T, Context], Coroutine[Any, Any, Component]]
73
- """Protocol definition for async function components."""
74
-
75
- FunctionComponent: TypeAlias = SyncFunctionComponent[T] | AsyncFunctionComponent[T]
76
- """Function component type."""
77
-
78
68
  # -- Context providers
79
69
 
80
70
 
@@ -97,7 +87,12 @@ class AsyncContextProvider(Protocol):
97
87
 
98
88
 
99
89
  ContextProvider: TypeAlias = SyncContextProvider | AsyncContextProvider
100
- """Context provider type."""
90
+ """
91
+ Sync or async context provider type.
92
+
93
+ Components can implement this protocol to add extra data to the rendering context
94
+ of their entire component subtree (including themselves).
95
+ """
101
96
 
102
97
  # -- Text processors
103
98
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: htmy
3
- Version: 0.5.0
3
+ Version: 0.7.0
4
4
  Summary: Async, pure-Python rendering engine.
5
5
  License: MIT
6
6
  Author: Peter Volf
@@ -30,13 +30,15 @@ Description-Content-Type: text/markdown
30
30
 
31
31
  **Async**, **pure-Python** rendering engine.
32
32
 
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
+
33
35
  ## Key features
34
36
 
35
37
  - **Async**-first, to let you make the best use of [modern async tools](https://github.com/timofurrer/awesome-asyncio).
36
38
  - **Powerful**, React-like **context support**, so you can avoid prop-drilling.
37
39
  - Sync and async **function components** with **decorator syntax**.
38
40
  - All baseline **HTML** tags built-in.
39
- - Support for **native HTML/XML** documents with dynamic formatting and **slot rendering**.
41
+ - Support for **native HTML/XML** documents with dynamic formatting and **slot rendering**, **without custom syntax**.
40
42
  - **Markdown** support with tools for customization.
41
43
  - Async, JSON based **internationalization**.
42
44
  - Built-in, easy to use `ErrorBoundary` component for graceful error handling.
@@ -45,6 +47,10 @@ Description-Content-Type: text/markdown
45
47
  - Automatic and customizable **property-name conversion** from snake case to kebab case.
46
48
  - **Fully-typed**.
47
49
 
50
+ ## Support
51
+
52
+ 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.
53
+
48
54
  ## Installation
49
55
 
50
56
  The package is available on PyPI and can be installed with:
@@ -61,9 +67,9 @@ Also, the library doesn't rely on advanced Python features such as metaclasses o
61
67
 
62
68
  ### Components
63
69
 
64
- Every class with a sync or async `htmy(context: Context) -> Component` method is an `htmy` component (technically an `HTMYComponentType`). Strings are also components, as well as lists or tuples of `HTMYComponentType` or string objects.
70
+ Every object with a sync or async `htmy(context: Context) -> Component` method is an `htmy` component (technically an `HTMYComponentType`). Strings are also components, as well as lists or tuples of `HTMYComponentType` or string objects. In many cases though, you don't even need to create components, simple functions that return components will be sufficient -- you can find out more about this in the [Components guide](https://volfpeter.github.io/htmy/components-guide/) of the documentation.
65
71
 
66
- Using this method name enables the conversion of any of your business objects (from `TypedDicts`s or `pydantic` models to ORM classes) into components without the fear of name collision with other tools.
72
+ Using the `htmy()` method name enables the conversion of any of your pre-existing business objects (from `TypedDicts`s or `pydantic` models to ORM classes) into components without the fear of name collision or compatibility issues with other tools.
67
73
 
68
74
  Async support makes it possible to load data or execute async business logic right in your components. This can reduce the amount of boilerplate you need to write in some cases, and also gives you the freedom to split the rendering and non-rendering logic in any way you see fit.
69
75
 
@@ -205,7 +211,7 @@ if __name__ == "__main__":
205
211
 
206
212
  As you could see from the code examples above, every component has a `context: Context` argument, which we haven't used so far. Context is a way to share data with the entire subtree of a component without "prop drilling".
207
213
 
208
- The context (technically a `Mapping`) is entirely managed by the renderer. Context provider components (any class with a sync or async `htmy_context() -> Context` method) add new data to the context to make it available to components in their subtree, and components can simply take what they need from the context.
214
+ The context (technically a `Mapping`) is entirely managed by the renderer. Context provider components (any class with a sync or async `htmy_context() -> Context` method) add new data to the context to make it available to components in their subtree (including themselves), and components can simply take what they need from the context.
209
215
 
210
216
  There is no restriction on what can be in the context, it can be used for anything the application needs, for example making the current user, UI preferences, themes, or formatters available to components. In fact, built-in components get their `Formatter` from the context if it contains one, to make it possible to customize tag property name and value formatting.
211
217
 
@@ -306,13 +312,13 @@ FastAPI:
306
312
 
307
313
  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).
308
314
 
309
- The main benefit of these frameworks is rapid application prototyping and a very convenient developer experience (at least as long as you stay within the built-in feature set of the framework). In exchange for that, they are very opinionated (from components to frontend tooling and state management), the underlying engineering is very complex, deployment and scaling can be hard or costly, and they can be hard to migrate away from. Even with these caveats, they can be a very good choice for internal tools and application prototyping.
315
+ The main benefit of these frameworks is rapid application prototyping and a very convenient developer experience, at least as long as you stay within the built-in feature set of the framework. In exchange for that, they are very opinionated (from components to frontend tooling and state management), the underlying engineering is very complex, deployment and scaling can be hard or costly, and they can be hard to migrate away from. Even with these caveats, they can be a very good choice for internal tools and application prototyping.
310
316
 
311
- The other end of spectrum -- plain rendering engines -- is dominated by the [Jinja](https://jinja.palletsprojects.com) templating engine, which is a safe choice as it has been and will be around for a long time. The main drawbacks with Jinja are the lack of good IDE support, the complete lack of static code analysis support, and the (subjectively) ugly syntax.
317
+ The other end of spectrum -- plain rendering engines -- is dominated by the [Jinja](https://jinja.palletsprojects.com) templating engine, which is a safe choice as it has been and will be around for a long time. The main drawbacks with Jinja are the lack of good IDE support, the complete lack of static code analysis support, and the (subjectively) ugly custom template syntax.
312
318
 
313
319
  Then there are tools that aim for the middleground, usually by providing most of the benefits and drawbacks of complete application frameworks while leaving state management, client-server communication, and dynamic UI updates for the user to solve, often with some level of [HTMX](https://htmx.org/) support. This group includes libraries like [FastHTML](https://github.com/answerdotai/fasthtml) and [Ludic](https://github.com/getludic/ludic).
314
320
 
315
- The primary aim of `htmy` is to be an **async**, pure-Python rendering engine, which is as **simple**, **maintainable**, and **customizable** as possible, while still providing all the building blocks for (conveniently) creating complex and maintainable applications.
321
+ The primary aim of `htmy` is to be a `Jinja` alternative that is similarly powerful and flexible, while also providing the benefits of full IDE support, static code analysis, and native Python (and HTML, XML, markdown) syntax. Additionally, `htmy` is **async-first**, so it works great with modern async Python frameworks such as [FastAPI](https://fastapi.tiangolo.com). The library was designed to be as **simple**, **maintainable**, and **customizable** as possible, while still providing all the building blocks for creating complex web applications.
316
322
 
317
323
  ## Dependencies
318
324
 
@@ -330,7 +336,13 @@ The documentation is built with `mkdocs-material` and `mkdocstrings`.
330
336
 
331
337
  ## Contributing
332
338
 
333
- All contributions are welcome, including more documentation, examples, code, and tests. Even questions.
339
+ We welcome contributions from the community to help improve the project! Whether you're an experienced developer or just starting out, there are many ways you can contribute:
340
+
341
+ - **Discuss**: Join our [Discussion Board](https://github.com/volfpeter/htmy/discussions) to ask questions, share ideas, provide feedback, and engage with the community.
342
+ - **Document**: Help improve the documentation by fixing typos, adding examples, and updating guides to make it easier for others to use the project.
343
+ - **Develop**: Prototype requested features or pick up issues from the issue tracker.
344
+ - **Share**: Share your own project by adding a link to it in the documentation, helping others discover and benefit from your work.
345
+ - **Test**: Write tests to improve coverage and enhance reliability.
334
346
 
335
347
  ## License - MIT
336
348
 
@@ -1,6 +1,7 @@
1
- htmy/__init__.py,sha256=dg1PFP21pFiWdM7eCpZiBCRHcNO4NJMT92FFqBi54Ig,2061
2
- htmy/core.py,sha256=5cqcxXRJGEsLrxyvoadfhcJEex2Vys-sJ6CpLbbmHQ4,17551
1
+ htmy/__init__.py,sha256=Us5P9Y6ZSp38poIz88bsAh2Hxuze5jE3V_uMtMyuH-E,1880
2
+ htmy/core.py,sha256=OoL11j2V-CfePC0dbkC2A5GbdK942b5Huszw3rLo7fc,15124
3
3
  htmy/etree.py,sha256=yKxom__AdsJY-Q1kbU0sdTMr0ZF5dMSVBKxayntNFyQ,3062
4
+ htmy/function_component.py,sha256=iSp5cGrErmIsc-VfNq053_J2m-Nuu_k2xK9UxvEnlw8,12431
4
5
  htmy/html.py,sha256=7UohfPRtl-3IoSbOiDxazsSHQpCZ0tyRdNayQISPM8A,21086
5
6
  htmy/i18n.py,sha256=brNazQjObBFfbnViZCpcnxa0qgxQbJfX7xJAH-MqTW8,5124
6
7
  htmy/io.py,sha256=iebJOZp7L0kZ9SWdqMatKtW5VGRIkEd-eD0_vTAldH8,41
@@ -12,9 +13,9 @@ htmy/renderer/__init__.py,sha256=xnP_aaoK-pTok-69wi8O_xlsgjoKTzWd2lIIeHGcuaY,226
12
13
  htmy/renderer/baseline.py,sha256=hHb7CoQhFFdD7Sdw0ltR1-XLGwE9pqmfL5yKFeF2rCg,4288
13
14
  htmy/renderer/default.py,sha256=lVMGuRybpFZ0u7pMB3IGOsFxw_rY8KqFQzWcNlmKCVI,10789
14
15
  htmy/snippet.py,sha256=dkHEOuULGsgawIMnSz99hghvNu8pLVGAQMQSlrn9ibY,10260
15
- htmy/typing.py,sha256=UE6tSMi-NyDazH4GKARx8dMxRFc6sSDIpLA5TSn0T7I,3346
16
+ htmy/typing.py,sha256=0spTpz_JWql2yy_lSlRx0uqgXar7fxwyBqWeIzltvKU,3111
16
17
  htmy/utils.py,sha256=Kp0j9G8CBeRiyFGmz-CoDiLtXHfpvHzlTVsWeDhIebM,1935
17
- htmy-0.5.0.dist-info/LICENSE,sha256=rFtoGU_3c_rlacXgOZapTHfMErN-JFPT5Bq_col4bqI,1067
18
- htmy-0.5.0.dist-info/METADATA,sha256=yA1X4Tp13Z5GcgyvDNwhTH8AoN93NX0KETLHYsIr1Gw,16550
19
- htmy-0.5.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
20
- htmy-0.5.0.dist-info/RECORD,,
18
+ htmy-0.7.0.dist-info/LICENSE,sha256=rFtoGU_3c_rlacXgOZapTHfMErN-JFPT5Bq_col4bqI,1067
19
+ htmy-0.7.0.dist-info/METADATA,sha256=5fzJ6L_K2WKV8-G-wa5a34IquAwdO5_v-DOWErOPRHg,18306
20
+ htmy-0.7.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
21
+ htmy-0.7.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.0.1
2
+ Generator: poetry-core 2.1.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
File without changes