htmy 0.3.6__py3-none-any.whl → 0.4.1__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.

Potentially problematic release.


This version of htmy might be problematic. Click here for more details.

htmy/__init__.py CHANGED
@@ -15,7 +15,7 @@ from .core import WithContext as WithContext
15
15
  from .core import XBool as XBool
16
16
  from .core import component as component
17
17
  from .core import xml_format_string as xml_format_string
18
- from .renderer import HTMY as HTMY
18
+ from .renderer import Renderer as Renderer
19
19
  from .typing import AsyncComponent as AsyncComponent
20
20
  from .typing import AsyncContextProvider as AsyncContextProvider
21
21
  from .typing import AsyncFunctionComponent as AsyncFunctionComponent
@@ -36,3 +36,6 @@ from .typing import SyncContextProvider as SyncContextProvider
36
36
  from .typing import SyncFunctionComponent as SyncFunctionComponent
37
37
  from .typing import is_component_sequence as is_component_sequence
38
38
  from .utils import join_components as join_components
39
+
40
+ HTMY = Renderer
41
+ """Deprecated alias for `Renderer`."""
htmy/core.py CHANGED
@@ -122,7 +122,7 @@ class WithContext(Fragment):
122
122
  self._context = context
123
123
 
124
124
  def htmy_context(self) -> Context:
125
- """Returns an HTMY context for child rendering."""
125
+ """Returns the context for child rendering."""
126
126
  return self._context
127
127
 
128
128
 
@@ -174,7 +174,7 @@ class Snippet:
174
174
  def _render_text(self, text: str, context: Context) -> Component:
175
175
  """
176
176
  Render function that takes the text that must be rendered and the current rendering context,
177
- and returns the corresponding HTMY component.
177
+ and returns the corresponding component.
178
178
  """
179
179
  return SafeStr(text)
180
180
 
@@ -523,7 +523,7 @@ class BaseTag(abc.ABC):
523
523
 
524
524
  @abc.abstractmethod
525
525
  def htmy(self, context: Context) -> Component:
526
- """Abstract base method for HTMY rendering."""
526
+ """Abstract base component implementation."""
527
527
  ...
528
528
 
529
529
  def _get_htmy_name(self) -> str:
htmy/etree.py CHANGED
@@ -17,7 +17,7 @@ from htmy.core import Fragment, SafeStr, WildcardTag
17
17
 
18
18
  class ETreeConverter:
19
19
  """
20
- Utility for converting XML strings to custom HTMY components.
20
+ Utility for converting XML strings to custom components.
21
21
  """
22
22
 
23
23
  __slots__ = ("_rules",)
@@ -33,12 +33,12 @@ class ETreeConverter:
33
33
  Initialization.
34
34
 
35
35
  Arguments:
36
- rules: Tag-name to HTMY component conversion rules.
36
+ rules: Tag-name to component conversion rules.
37
37
  """
38
38
  self._rules = rules
39
39
 
40
40
  def convert(self, element: str) -> ComponentType:
41
- """Converts the given (possible multi-root) XML string to an HTMY component."""
41
+ """Converts the given (possibly multi-root) XML string to a component."""
42
42
  if len(self._rules) == 0:
43
43
  return SafeStr(element)
44
44
 
@@ -46,7 +46,7 @@ class ETreeConverter:
46
46
  return self.convert_element(ET.fromstring(element)) # noqa: S314 # Only use from XML strings from a trusted source.
47
47
 
48
48
  def convert_element(self, element: Element) -> ComponentType:
49
- """Converts the given `Element` to an HTMY component."""
49
+ """Converts the given `Element` to a component."""
50
50
  rules = self._rules
51
51
  if len(rules) == 0:
52
52
  return SafeStr(ET.tostring(element))
@@ -67,7 +67,7 @@ class ETreeConverter:
67
67
 
68
68
  def _convert_properties(self, element: Element) -> Properties:
69
69
  """
70
- Converts the attributes of the given `Element` to an HTMY `Properties` mapping.
70
+ Converts the attributes of the given `Element` to a `Properties` mapping.
71
71
 
72
72
  This method should not alter property names in any way.
73
73
  """
@@ -75,8 +75,8 @@ class ETreeConverter:
75
75
 
76
76
  def _convert_children(self, element: Element) -> Generator[ComponentType, None, None]:
77
77
  """
78
- Generator that converts all (text and `Element`) children of the given `Element`
79
- into an HTMY component."""
78
+ Generator that converts all (text and `Element`) children of the given `Element` to a component.
79
+ """
80
80
  if text := self._process_text(element.text):
81
81
  yield text
82
82
 
htmy/html.py CHANGED
@@ -859,6 +859,16 @@ class sup(Tag):
859
859
  tag_config = _DefaultTagConfig.inline_children
860
860
 
861
861
 
862
+ class svg(Tag):
863
+ """
864
+ `<svg>` element.
865
+
866
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg.
867
+ """
868
+
869
+ __slots__ = ()
870
+
871
+
862
872
  class u(Tag):
863
873
  """
864
874
  `<u>` element.
htmy/md/core.py CHANGED
@@ -99,9 +99,9 @@ class MD(Snippet):
99
99
  Arguments:
100
100
  path_or_text: The path where the markdown file is located or a markdown `Text`.
101
101
  converter: Function that converts an HTML string (the parsed and processed markdown text)
102
- into an HTMY component.
103
- renderer: Function that get the parsed and converted content and the metadata (if it exists)
104
- and turns them into an HTMY component.
102
+ into a component.
103
+ renderer: Function that gets the parsed and converted content and the metadata (if it exists)
104
+ and turns them into a component.
105
105
  text_processor: An optional text processors that can be used to process the text
106
106
  content before rendering. It can be used for example for token replacement or
107
107
  string formatting.
@@ -0,0 +1,8 @@
1
+ from .baseline import Renderer as _BaselineRenderer
2
+ from .default import Renderer as _DefaultRenderer
3
+
4
+ Renderer = _DefaultRenderer
5
+ """The default renderer."""
6
+
7
+ BaselineRenderer = _BaselineRenderer
8
+ """The baseline renderer."""
@@ -4,12 +4,21 @@ import asyncio
4
4
  from collections import ChainMap
5
5
  from collections.abc import Awaitable, Callable, Iterable
6
6
 
7
- from .core import ErrorBoundary, xml_format_string
8
- from .typing import Component, ComponentType, Context
7
+ from htmy.core import ErrorBoundary, xml_format_string
8
+ from htmy.typing import Component, ComponentType, Context
9
9
 
10
10
 
11
- class HTMY:
12
- """HTMY component renderer."""
11
+ class Renderer:
12
+ """
13
+ The baseline component renderer.
14
+
15
+ Because of the simple, recursive implementation, this renderer is the easiest to reason about.
16
+ Therefore it is useful for validating component correctness before bug reporting (if another
17
+ renderer implementation fails), testing and debugging alternative implementations, and it can
18
+ also serve as the baseline for benchmarking optimized renderers.
19
+
20
+ The performance of this renderer is not production quality.
21
+ """
13
22
 
14
23
  __slots__ = ("_default_context", "_string_formatter")
15
24
 
@@ -0,0 +1,282 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections import ChainMap, deque
5
+ from collections.abc import Awaitable, Callable, Iterator
6
+ from typing import TypeAlias
7
+
8
+ from htmy.core import ErrorBoundary, xml_format_string
9
+ from htmy.typing import Component, ComponentType, Context, ContextProvider, is_component_sequence
10
+
11
+
12
+ class _Node:
13
+ """A single node in the linked list the renderer constructs to resolve a component tree."""
14
+
15
+ __slots__ = ("component", "next")
16
+
17
+ def __init__(self, component: ComponentType, next: _Node | None = None) -> None:
18
+ """
19
+ Initialization.
20
+
21
+ Arguments:
22
+ component: The component in this node.
23
+ next: The next component in the list, if there is one.
24
+ """
25
+ self.component = component
26
+ self.next = next
27
+
28
+ def iter_nodes(self, *, include_self: bool = True) -> Iterator[_Node]:
29
+ """
30
+ Iterates over all following nodes.
31
+
32
+ Arguments:
33
+ include_self: Whether the node on which this method is called should also
34
+ be included in the iterator.
35
+ """
36
+ current = self if include_self else self.next
37
+ while current is not None:
38
+ yield current
39
+ current = current.next
40
+
41
+
42
+ _NodeAndChildContext: TypeAlias = tuple[_Node, Context]
43
+
44
+
45
+ class _ComponentRenderer:
46
+ """
47
+ `ComponentType` renderer that converts a component tree into a linked list of resolved (`str`) nodes.
48
+ """
49
+
50
+ __slots__ = ("_async_todos", "_error_boundary_todos", "_sync_todos", "_root", "_string_formatter")
51
+
52
+ def __init__(
53
+ self,
54
+ component: ComponentType,
55
+ context: Context,
56
+ *,
57
+ string_formatter: Callable[[str], str],
58
+ ) -> None:
59
+ """
60
+ Initialization.
61
+
62
+ Arguments:
63
+ component: The component to render.
64
+ context: The base context to use for rendering the component.
65
+ string_formatter: The string formatter to use.
66
+ """
67
+ self._async_todos: deque[_NodeAndChildContext] = deque()
68
+ """Async node - context tuples that need to be rendered."""
69
+ self._error_boundary_todos: deque[_NodeAndChildContext] = deque()
70
+ """Node context tuples where `node.component` is an `ErrorBoundary`."""
71
+ self._sync_todos: deque[_NodeAndChildContext] = deque()
72
+ """
73
+ Sync node - context tuples that need to be rendered (`node.component` is an `HTMYComponentType`).
74
+ """
75
+ self._string_formatter = string_formatter
76
+ """The string formatter to use."""
77
+
78
+ if isinstance(component, str):
79
+ root = _Node(string_formatter(component), None)
80
+ else:
81
+ root = _Node(component, None)
82
+ self._schedule_node(root, context)
83
+ self._root = root
84
+ """The root node in the linked list the renderer constructs."""
85
+
86
+ async def _extend_context(self, component: ContextProvider, context: Context) -> Context:
87
+ """
88
+ Returns a new context from the given component and context.
89
+
90
+ Arguments:
91
+ component: A `ContextProvider` component.
92
+ context: The current rendering context.
93
+ """
94
+ extra_context: Context | Awaitable[Context] = component.htmy_context()
95
+ if isinstance(extra_context, Awaitable):
96
+ extra_context = await extra_context
97
+
98
+ return (
99
+ # Context must not be mutated. We can ignore that ChainMap expects mutable mappings.
100
+ ChainMap(extra_context, context) # type: ignore[arg-type]
101
+ if extra_context
102
+ else context
103
+ )
104
+
105
+ async def _process_error_boundary(self, node: _Node, context: Context) -> None:
106
+ """
107
+ Processes a single node whose component is an `ErrorBoundary`.
108
+ """
109
+ component: ErrorBoundary = node.component # type: ignore[assignment]
110
+ if hasattr(component, "htmy_context"): # isinstance() is too expensive.
111
+ context = await self._extend_context(component, context)
112
+
113
+ try:
114
+ result = await _render_component(
115
+ component.htmy(context),
116
+ context=context,
117
+ string_formatter=self._string_formatter,
118
+ )
119
+ except Exception as e:
120
+ renderer = _ComponentRenderer(
121
+ component.fallback_component(e),
122
+ context,
123
+ string_formatter=self._string_formatter,
124
+ )
125
+ result = await renderer.run()
126
+
127
+ node.component = result # No string formatting.
128
+
129
+ def _process_node_result(self, parent_node: _Node, component: Component, context: Context) -> None:
130
+ """
131
+ Processes the result of a single node.
132
+
133
+ Arguments:
134
+ parent_node: The node that was resolved.
135
+ component: The (awaited if async) result of `parent_node.component.htmy()`.
136
+ context: The context that was used for rendering `parent_node.component`.
137
+ """
138
+ schedule_node = self._schedule_node
139
+ string_formatter = self._string_formatter
140
+ if is_component_sequence(component):
141
+ if len(component) == 0:
142
+ parent_node.component = ""
143
+ return
144
+
145
+ first_comp, *rest_comps = component
146
+ if isinstance(first_comp, str):
147
+ parent_node.component = string_formatter(first_comp)
148
+ else:
149
+ parent_node.component = first_comp
150
+ schedule_node(parent_node, context)
151
+
152
+ old_next = parent_node.next
153
+ last: _Node = parent_node
154
+ for c in rest_comps:
155
+ if isinstance(c, str):
156
+ node = _Node(string_formatter(c), old_next)
157
+ else:
158
+ node = _Node(c, old_next)
159
+ schedule_node(node, context)
160
+
161
+ last.next = node
162
+ last = node
163
+ elif isinstance(component, str):
164
+ parent_node.component = string_formatter(component)
165
+ else:
166
+ parent_node.component = component # type: ignore[assignment]
167
+ schedule_node(parent_node, context)
168
+
169
+ async def _process_async_node(self, node: _Node, context: Context) -> None:
170
+ """
171
+ Processes the given node. `node.component` must be an async component.
172
+ """
173
+ result = await node.component.htmy(context) # type: ignore[misc,union-attr]
174
+ self._process_node_result(node, result, context)
175
+
176
+ def _schedule_node(self, node: _Node, child_context: Context) -> None:
177
+ """
178
+ Schedules the given node for rendering with the given child context.
179
+
180
+ `node.component` must be an `HTMYComponentType` (single component and not `str`).
181
+ """
182
+ component = node.component
183
+ if asyncio.iscoroutinefunction(component.htmy): # type: ignore[union-attr]
184
+ self._async_todos.append((node, child_context))
185
+ elif isinstance(component, ErrorBoundary):
186
+ self._error_boundary_todos.append((node, child_context))
187
+ else:
188
+ self._sync_todos.append((node, child_context))
189
+
190
+ async def run(self) -> str:
191
+ """Runs the component renderer."""
192
+ async_todos = self._async_todos
193
+ sync_todos = self._sync_todos
194
+ process_node_result = self._process_node_result
195
+ process_async_node = self._process_async_node
196
+
197
+ while sync_todos or async_todos:
198
+ while sync_todos:
199
+ node, child_context = sync_todos.pop()
200
+ component = node.component
201
+ if hasattr(component, "htmy_context"): # isinstance() is too expensive.
202
+ child_context = await self._extend_context(component, child_context) # type: ignore[arg-type]
203
+
204
+ if asyncio.iscoroutinefunction(node.component.htmy): # type: ignore[union-attr]
205
+ async_todos.append((node, child_context))
206
+ else:
207
+ result: Component = node.component.htmy(child_context) # type: ignore[assignment,union-attr]
208
+ process_node_result(node, result, child_context)
209
+
210
+ if async_todos:
211
+ await asyncio.gather(*(process_async_node(n, ctx) for n, ctx in async_todos))
212
+ async_todos.clear()
213
+
214
+ if self._error_boundary_todos:
215
+ await asyncio.gather(
216
+ *(self._process_error_boundary(n, ctx) for n, ctx in self._error_boundary_todos)
217
+ )
218
+
219
+ return "".join(node.component for node in self._root.iter_nodes()) # type: ignore[misc]
220
+
221
+
222
+ async def _render_component(
223
+ component: Component,
224
+ *,
225
+ context: Context,
226
+ string_formatter: Callable[[str], str],
227
+ ) -> str:
228
+ """Renders the given component with the given settings."""
229
+ if is_component_sequence(component):
230
+ if len(component) == 0:
231
+ return ""
232
+
233
+ renderers = (_ComponentRenderer(c, context, string_formatter=string_formatter) for c in component)
234
+ return "".join(await asyncio.gather(*(r.run() for r in renderers)))
235
+ else:
236
+ return await _ComponentRenderer(component, context, string_formatter=string_formatter).run() # type: ignore[arg-type]
237
+
238
+
239
+ class Renderer:
240
+ """
241
+ The default renderer.
242
+
243
+ It resolves component trees by converting them to a linked list of resolved component parts
244
+ before combining them to the final string.
245
+ """
246
+
247
+ __slots__ = ("_default_context", "_string_formatter")
248
+
249
+ def __init__(
250
+ self,
251
+ default_context: Context | None = None,
252
+ *,
253
+ string_formatter: Callable[[str], str] = xml_format_string,
254
+ ) -> None:
255
+ """
256
+ Initialization.
257
+
258
+ Arguments:
259
+ default_context: The default context to use for rendering if `render()` doesn't
260
+ receive a context.
261
+ string_formatter: Callable that should be used to format plain strings. By default
262
+ an XML-safe string formatter will be used.
263
+ """
264
+ self._default_context: Context = {} if default_context is None else default_context
265
+ self._string_formatter = string_formatter
266
+
267
+ async def render(self, component: Component, context: Context | None = None) -> str:
268
+ """
269
+ Renders the given component.
270
+
271
+ Arguments:
272
+ component: The component to render.
273
+ context: An optional rendering context.
274
+
275
+ Returns:
276
+ The rendered string.
277
+ """
278
+ # Type ignore: ChainMap expects mutable mappings, but context mutation is not allowed so don't care.
279
+ context = (
280
+ self._default_context if context is None else ChainMap(context, self._default_context) # type: ignore[arg-type]
281
+ )
282
+ return await _render_component(component, context=context, string_formatter=self._string_formatter)
htmy/typing.py CHANGED
@@ -88,7 +88,7 @@ class SyncContextProvider(Protocol):
88
88
  """Protocol definition for sync context providers."""
89
89
 
90
90
  def htmy_context(self) -> Context:
91
- """Returns an HTMY context for child rendering."""
91
+ """Returns a context for child rendering."""
92
92
  ...
93
93
 
94
94
 
@@ -97,7 +97,7 @@ class AsyncContextProvider(Protocol):
97
97
  """Protocol definition for async context providers."""
98
98
 
99
99
  async def htmy_context(self) -> Context:
100
- """Returns an HTMY context for child rendering."""
100
+ """Returns a context for child rendering."""
101
101
  ...
102
102
 
103
103
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: htmy
3
- Version: 0.3.6
3
+ Version: 0.4.1
4
4
  Summary: Async, pure-Python rendering engine.
5
5
  License: MIT
6
6
  Author: Peter Volf
@@ -170,16 +170,16 @@ user_table = html.table(
170
170
 
171
171
  ### Rendering
172
172
 
173
- `htmy.HTMY` is the built-in, default renderer of the library.
173
+ `htmy.Renderer` is the built-in, default renderer of the library.
174
174
 
175
- If you're using the library in an async web framework like [FastAPI](https://fastapi.tiangolo.com/), then you're already in an async environment, so you can render components as simply as this: `await HTMY().render(my_root_component)`.
175
+ If you're using the library in an async web framework like [FastAPI](https://fastapi.tiangolo.com/), then you're already in an async environment, so you can render components as simply as this: `await Renderer().render(my_root_component)`.
176
176
 
177
177
  If you're trying to run the renderer in a sync environment, like a local script or CLI, then you first need to wrap the renderer in an async task and execute that task with `asyncio.run()`:
178
178
 
179
179
  ```python
180
180
  import asyncio
181
181
 
182
- from htmy import HTMY, html
182
+ from htmy import Renderer, html
183
183
 
184
184
  async def render_page() -> None:
185
185
  page = (
@@ -192,7 +192,7 @@ async def render_page() -> None:
192
192
  )
193
193
  )
194
194
 
195
- result = await HTMY().render(page)
195
+ result = await Renderer().render(page)
196
196
  print(result)
197
197
 
198
198
 
@@ -213,7 +213,7 @@ Here's an example context provider and consumer implementation:
213
213
  ```python
214
214
  import asyncio
215
215
 
216
- from htmy import HTMY, Component, ComponentType, Context, component, html
216
+ from htmy import Component, ComponentType, Context, Renderer, component, html
217
217
 
218
218
  class UserContext:
219
219
  def __init__(self, *children: ComponentType, username: str, theme: str) -> None:
@@ -259,7 +259,7 @@ async def render_welcome_page() -> None:
259
259
  theme="dark",
260
260
  )
261
261
 
262
- result = await HTMY().render(page)
262
+ result = await Renderer().render(page)
263
263
  print(result)
264
264
 
265
265
  if __name__ == "__main__":
@@ -270,7 +270,7 @@ You can of course rely on the built-in context related utilities like the `Conte
270
270
 
271
271
  ### Formatter
272
272
 
273
- As mentioned before, the built-in `Formatter` class is responsible for tag attribute name and value formatting. You can completely override or extend the built-in formatting behavior simply by extending this class or adding new rules to an instance of it, and then adding the custom instance to the context, either directly in `HTMY` or `HTMY.render()`, or in a context provider component.
273
+ As mentioned before, the built-in `Formatter` class is responsible for tag attribute name and value formatting. You can completely override or extend the built-in formatting behavior simply by extending this class or adding new rules to an instance of it, and then adding the custom instance to the context, either directly in `Renderer` or `Renderer.render()`, or in a context provider component.
274
274
 
275
275
  These are default tag attribute formatting rules:
276
276
 
@@ -0,0 +1,19 @@
1
+ htmy/__init__.py,sha256=yMPXQHkXQCjyx7UUVcfsMQ_5YjvNT62Kb9TI1xEcw2A,1899
2
+ htmy/core.py,sha256=5V019PwIes9Eyzu0eWwsvBZNZ6x7DhQy9y99myCw9A0,19322
3
+ htmy/etree.py,sha256=yKxom__AdsJY-Q1kbU0sdTMr0ZF5dMSVBKxayntNFyQ,3062
4
+ htmy/html.py,sha256=7UohfPRtl-3IoSbOiDxazsSHQpCZ0tyRdNayQISPM8A,21086
5
+ htmy/i18n.py,sha256=brNazQjObBFfbnViZCpcnxa0qgxQbJfX7xJAH-MqTW8,5124
6
+ htmy/io.py,sha256=iebJOZp7L0kZ9SWdqMatKtW5VGRIkEd-eD0_vTAldH8,41
7
+ htmy/md/__init__.py,sha256=lxBJnYplkDuxYuiese6My9KYp1DeGdzo70iUdYTvMnE,334
8
+ htmy/md/core.py,sha256=EK-QoB2BIra3o1nvTK0lGP3mQQPtRXuE6zBKF_IWR_o,3675
9
+ htmy/md/typing.py,sha256=LF-AEvo7FCW2KumyR5l55rsXizV2E4AHVLKFf6lApgM,762
10
+ htmy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ htmy/renderer/__init__.py,sha256=xnP_aaoK-pTok-69wi8O_xlsgjoKTzWd2lIIeHGcuaY,226
12
+ htmy/renderer/baseline.py,sha256=hHb7CoQhFFdD7Sdw0ltR1-XLGwE9pqmfL5yKFeF2rCg,4288
13
+ htmy/renderer/default.py,sha256=rdx-yFYz-cz197xfe9co8Lru2cdZxAjOO4dqY250Y1Q,10767
14
+ htmy/typing.py,sha256=f4QZ8vQL7JfN402yDb8Hq_DYvQS_GUgdXK8-xTBM8y8,3122
15
+ htmy/utils.py,sha256=7_CyA39l2m6jzDqparPKkKgRB2wiGuBZXbiPgiZOXKA,1093
16
+ htmy-0.4.1.dist-info/LICENSE,sha256=rFtoGU_3c_rlacXgOZapTHfMErN-JFPT5Bq_col4bqI,1067
17
+ htmy-0.4.1.dist-info/METADATA,sha256=-xo4VOq4dtGVSfQrLYXPgM701HztcohcK4CXga2IFRo,16379
18
+ htmy-0.4.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
19
+ htmy-0.4.1.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- htmy/__init__.py,sha256=My_Dmh9JNQpQekTO6CEbdbV7Ux7MD9Pz-L2M3KMZjGQ,1835
2
- htmy/core.py,sha256=kdIE9s7mE-mphPraZvl5h7RL9NBVOYaRWsWsA0PpphM,19332
3
- htmy/etree.py,sha256=zZkKY82t5fX85unS9oHuG6KEBsJY_iz6E7SJto8lSVQ,3097
4
- htmy/html.py,sha256=pxmE-KU5OgwNp6MyxOfdS0Ohpzu2RNYCeGGFlHLDGUM,20940
5
- htmy/i18n.py,sha256=brNazQjObBFfbnViZCpcnxa0qgxQbJfX7xJAH-MqTW8,5124
6
- htmy/io.py,sha256=iebJOZp7L0kZ9SWdqMatKtW5VGRIkEd-eD0_vTAldH8,41
7
- htmy/md/__init__.py,sha256=lxBJnYplkDuxYuiese6My9KYp1DeGdzo70iUdYTvMnE,334
8
- htmy/md/core.py,sha256=5fLEihxjBivknZUwiR13gc924K2_jnthv9xnSDsTfow,3686
9
- htmy/md/typing.py,sha256=LF-AEvo7FCW2KumyR5l55rsXizV2E4AHVLKFf6lApgM,762
10
- htmy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- htmy/renderer.py,sha256=vCYqq83RaEtlXKDGWB6LgKjoE1TSfMfB5TW1lJWHE5c,3829
12
- htmy/typing.py,sha256=-_N9xE9bh0OvP2LRedh3OeuOGlJcipxm0Aul73IKN7E,3134
13
- htmy/utils.py,sha256=7_CyA39l2m6jzDqparPKkKgRB2wiGuBZXbiPgiZOXKA,1093
14
- htmy-0.3.6.dist-info/LICENSE,sha256=rFtoGU_3c_rlacXgOZapTHfMErN-JFPT5Bq_col4bqI,1067
15
- htmy-0.3.6.dist-info/METADATA,sha256=M0UqpgexbJ0963aacW3pVvd_puJu99S72yrmIrL4Pus,16347
16
- htmy-0.3.6.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
- htmy-0.3.6.dist-info/RECORD,,
File without changes
File without changes