htmy 0.3.4__tar.gz → 0.3.6__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: htmy
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: Async, pure-Python rendering engine.
5
5
  License: MIT
6
6
  Author: Peter Volf
@@ -295,6 +295,12 @@ If a component executes a potentially "long-running" synchronous call, it is str
295
295
 
296
296
  In all other cases, it's best to use sync components.
297
297
 
298
+ ## Framework integrations
299
+
300
+ FastAPI:
301
+
302
+ - [FastHX](https://github.com/volfpeter/fasthx)
303
+
298
304
  ## Why
299
305
 
300
306
  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).
@@ -276,6 +276,12 @@ If a component executes a potentially "long-running" synchronous call, it is str
276
276
 
277
277
  In all other cases, it's best to use sync components.
278
278
 
279
+ ## Framework integrations
280
+
281
+ FastAPI:
282
+
283
+ - [FastHX](https://github.com/volfpeter/fasthx)
284
+
279
285
  ## Why
280
286
 
281
287
  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).
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import abc
4
4
  import asyncio
5
5
  import enum
6
- from collections.abc import Callable, Container
6
+ from collections.abc import Awaitable, Callable, Container
7
7
  from pathlib import Path
8
8
  from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypedDict, cast, overload
9
9
  from xml.sax.saxutils import escape as xml_escape
@@ -21,6 +21,7 @@ from .typing import (
21
21
  PropertyValue,
22
22
  SyncFunctionComponent,
23
23
  T,
24
+ TextProcessor,
24
25
  is_component_sequence,
25
26
  )
26
27
  from .utils import join_components
@@ -130,21 +131,34 @@ class Snippet:
130
131
  Base component that can load its content from a file.
131
132
  """
132
133
 
133
- __slots__ = ("_path_or_text",)
134
+ __slots__ = ("_path_or_text", "_text_processor")
134
135
 
135
- def __init__(self, path_or_text: Text | str | Path) -> None:
136
+ def __init__(
137
+ self,
138
+ path_or_text: Text | str | Path,
139
+ *,
140
+ text_processor: TextProcessor | None = None,
141
+ ) -> None:
136
142
  """
137
143
  Initialization.
138
144
 
139
145
  Arguments:
140
146
  path_or_text: The path from where the content should be loaded or a `Text`
141
147
  instance if this value should be rendered directly.
148
+ text_processor: An optional text processors that can be used to process the text
149
+ content before rendering. It can be used for example for token replacement or
150
+ string formatting.
142
151
  """
143
152
  self._path_or_text = path_or_text
153
+ self._text_processor = text_processor
144
154
 
145
155
  async def htmy(self, context: Context) -> Component:
146
156
  """Renders the component."""
147
157
  text = await self._get_text_content()
158
+ if self._text_processor is not None:
159
+ processed = self._text_processor(text, context)
160
+ text = (await processed) if isinstance(processed, Awaitable) else processed
161
+
148
162
  return self._render_text(text, context)
149
163
 
150
164
  async def _get_text_content(self) -> str:
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, ClassVar
5
5
  from markdown import Markdown
6
6
 
7
7
  from htmy.core import ContextAware, SafeStr, Snippet, Text
8
+ from htmy.typing import TextProcessor
8
9
 
9
10
  if TYPE_CHECKING:
10
11
  from collections.abc import Callable
@@ -90,6 +91,7 @@ class MD(Snippet):
90
91
  *,
91
92
  converter: Callable[[str], Component] | None = None,
92
93
  renderer: MarkdownRenderFunction | None = None,
94
+ text_processor: TextProcessor | None = None,
93
95
  ) -> None:
94
96
  """
95
97
  Initialization.
@@ -100,8 +102,11 @@ class MD(Snippet):
100
102
  into an HTMY component.
101
103
  renderer: Function that get the parsed and converted content and the metadata (if it exists)
102
104
  and turns them into an HTMY component.
105
+ text_processor: An optional text processors that can be used to process the text
106
+ content before rendering. It can be used for example for token replacement or
107
+ string formatting.
103
108
  """
104
- super().__init__(path_or_text)
109
+ super().__init__(path_or_text, text_processor=text_processor)
105
110
  self._converter: Callable[[str], Component] = SafeStr if converter is None else converter
106
111
  self._renderer = renderer
107
112
 
@@ -5,7 +5,7 @@ from collections import ChainMap
5
5
  from collections.abc import Awaitable, Callable, Iterable
6
6
 
7
7
  from .core import ErrorBoundary, xml_format_string
8
- from .typing import Component, ComponentType, Context, ContextProvider, HTMYComponentType
8
+ from .typing import Component, ComponentType, Context
9
9
 
10
10
 
11
11
  class HTMY:
@@ -82,15 +82,19 @@ class HTMY:
82
82
  Returns:
83
83
  The rendered string.
84
84
  """
85
- child_context = context
86
- if isinstance(component, ContextProvider):
87
- extra_context = component.htmy_context()
88
- if isinstance(extra_context, Awaitable):
89
- extra_context = await extra_context
85
+ if isinstance(component, str):
86
+ return self._string_formatter(component)
87
+ else:
88
+ child_context: Context = context
89
+ if hasattr(component, "htmy_context"): # isinstance() is too expensive.
90
+ extra_context: Context | Awaitable[Context] = component.htmy_context()
91
+ if isinstance(extra_context, Awaitable):
92
+ extra_context = await extra_context
90
93
 
91
- child_context = ChainMap(extra_context, context)
94
+ if len(extra_context):
95
+ # Context must not be mutated, so we can ignore that ChainMap expext mutable mappings.
96
+ child_context = ChainMap(extra_context, context) # type: ignore[arg-type]
92
97
 
93
- if isinstance(component, HTMYComponentType):
94
98
  try:
95
99
  children = component.htmy(child_context)
96
100
  if isinstance(children, Awaitable):
@@ -102,7 +106,3 @@ class HTMY:
102
106
  return await self._render_one(component.fallback_component(e), context)
103
107
 
104
108
  raise e
105
- elif isinstance(component, str):
106
- return self._string_formatter(component)
107
- else:
108
- raise TypeError("Unknown component type.")
@@ -103,3 +103,9 @@ class AsyncContextProvider(Protocol):
103
103
 
104
104
  ContextProvider: TypeAlias = SyncContextProvider | AsyncContextProvider
105
105
  """Context provider type."""
106
+
107
+
108
+ # -- Text processors
109
+
110
+ TextProcessor: TypeAlias = Callable[[str, Context], str | Coroutine[Any, Any, str]]
111
+ """Callable type that expects a string and a context, and returns a processed string."""
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "htmy"
3
- version = "0.3.4"
3
+ version = "0.3.6"
4
4
  description = "Async, pure-Python rendering engine."
5
5
  authors = ["Peter Volf <do.volfp@gmail.com>"]
6
6
  license = "MIT"
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