源代码:https://github.com/volfpeter/htmy
文档和示例:https://volfpeter.github.io/htmy
htmy异步,纯净的python渲染引擎。
ErrorBoundary组件,用于优雅的错误处理。该包在PYPI上可用,可以安装:
$ pip install htmy 从渲染引擎本身到内置组件,整个库都是围绕一些简单的协议和少数简单的公用事业类构建的。这意味着您可以轻松自定义,扩展或替换库中的所有内容。是的,甚至是渲染引擎。其余部分将继续按预期工作。
另外,库不依赖高级Python功能,例如元组或描述符。也没有复杂的基础类别。即使是初级工程师也可以理解,开发和调试与htmy构建的应用程序。
每个具有同步或异步htmy(context: Context) -> Component方法是htmy组件(从技术上讲是HTMYComponentType )。字符串也是组件,也是HTMYComponentType或字符串对象的列表或元组。
使用此方法名称可以使您的任何业务对象(从TypedDicts或pydantic模型到ORM类)转换为组件,而不必担心名称与其他工具相撞。
异步支持使得可以在组件中加载数据或执行异步业务逻辑。在某些情况下,这可以减少您需要编写的样板量,还可以自由以任何方式分配渲染和非渲染逻辑。
例子:
from dataclasses import dataclass
from htmy import Component , Context , html
@ dataclass ( frozen = True , kw_only = True , slots = True )
class User :
username : str
name : str
email : str
async def is_admin ( self ) -> bool :
return False
class UserRow ( User ):
async def htmy ( self , context : Context ) -> Component :
role = "admin" if await self . is_admin () else "restricted"
return html . tr (
html . td ( self . username ),
html . td ( self . name ),
html . td ( html . a ( self . email , href = f"mailto: { self . email } " )),
html . td ( role )
)
@ dataclass ( frozen = True , kw_only = True , slots = True )
class UserRows :
users : list [ User ]
def htmy ( self , context : Context ) -> Component :
# Note that a list is returned here. A list or tuple of `HTMYComponentType | str` objects is also a component.
return [ UserRow ( username = u . username , name = u . name , email = u . email ) for u in self . users ]
user_table = html . table (
UserRows (
users = [
User ( username = "Foo" , name = "Foo" , email = "[email protected]" ),
User ( username = "Bar" , name = "Bar" , email = "[email protected]" ),
]
)
) htmy还提供了一个@component Decorator,可用于同步或异步my_component(props: MyProps, context: Context) -> Component函数将它们转换为组件(保留props键入)。
这是与上述相同的示例,但具有函数组件:
from dataclasses import dataclass
from htmy import Component , Context , component , html
@ dataclass ( frozen = True , kw_only = True , slots = True )
class User :
username : str
name : str
email : str
async def is_admin ( self ) -> bool :
return False
@ component
async def user_row ( user : User , context : Context ) -> Component :
# The first argument of function components is their "props", the data they need.
# The second argument is the rendering context.
role = "admin" if await user . is_admin () else "restricted"
return html . tr (
html . td ( user . username ),
html . td ( user . name ),
html . td ( html . a ( user . email , href = f"mailto: { user . email } " )),
html . td ( role )
)
@ component
def user_rows ( users : list [ User ], context : Context ) -> Component :
# Nothing to await in this component, so it's sync.
# Note that we only pass the "props" to the user_row() component (well, function component wrapper).
# The context will be passed to the wrapper during rendering.
return [ user_row ( user ) for user in users ]
user_table = html . table (
user_rows (
[
User ( username = "Foo" , name = "Foo" , email = "[email protected]" ),
User ( username = "Bar" , name = "Bar" , email = "[email protected]" ),
]
)
)htmy为HTML和其他用例都有丰富的内置实用程序和组件:
html模块:一组基线HTML标签。md :用于加载,解析,转换和渲染Markdown内容的MarkdownParser实用程序和MD组件。i18n :基于JSON的异步实用程序。BaseTag , TagWithProps , Tag , WildcardTag :自定义XML标签的基类。ErrorBoundary , Fragment , SafeStr , WithContext :用于错误处理的实用程序,组件包装器,上下文提供商和格式。Snippet :用于从文件系统加载和自定义文档摘要的实用程序类。etree.ETreeConverter :将XML转换为组件树的实用程序,并支持自定义HTMY组件。htmy.HTMY是库的内置,默认的渲染器。
如果您在诸如FastApi之类的异步网络框架中使用库,那么您已经处于异步环境中,因此您可以简单地渲染组件: await HTMY().render(my_root_component) 。
如果您试图在同步环境中运行渲染器,例如本地脚本或CLI,则首先需要将渲染器包装在异步任务中,并使用asyncio.run()执行该任务:
import asyncio
from htmy import HTMY , html
async def render_page () -> None :
page = (
html . DOCTYPE . html ,
html . html (
html . body (
html . h1 ( "Hello World!" ),
html . p ( "This page was rendered by " , html . code ( "htmy" )),
),
)
)
result = await HTMY (). render ( page )
print ( result )
if __name__ == "__main__" :
asyncio . run ( render_page ())从上面的代码示例中可以看到,每个组件都有一个context: Context参数,我们迄今尚未使用。上下文是一种与组件的整个子树共享数据的方法,而无需“支撑钻探”。
上下文(从技术上讲, Mapping )完全由渲染器管理。上下文提供商组件(具有同步或异步htmy_context() -> Context方法的任何类)将新数据添加到上下文中,以使其可用于其子树中的组件,并且组件可以简单地从上下文中获取所需的东西。
上下文中可以使用的内容没有限制,可以用于应用程序所需的任何内容,例如使当前用户,UI首选项,主题或格式用于组件。实际上,内置组件(如果包含一个组件)将其Formatter从上下文中获取,以便可以自定义标记属性名称和值格式。
这是一个示例上下文提供商和消费者实施:
import asyncio
from htmy import HTMY , Component , ComponentType , Context , component , html
class UserContext :
def __init__ ( self , * children : ComponentType , username : str , theme : str ) -> None :
self . _children = children
self . username = username
self . theme = theme
def htmy_context ( self ) -> Context :
# Context provider implementation.
return { UserContext : self }
def htmy ( self , context : Context ) -> Component :
# Context providers must also be components, as they just
# wrap some children components in their context.
return self . _children
@ classmethod
def from_context ( cls , context : Context ) -> "UserContext" :
user_context = context [ cls ]
if isinstance ( user_context , UserContext ):
return user_context
raise TypeError ( "Invalid user context." )
@ component
def welcome_page ( text : str , context : Context ) -> Component :
# Get user information from the context.
user = UserContext . from_context ( context )
return (
html . DOCTYPE . html ,
html . html (
html . body (
html . h1 ( text , html . strong ( user . username )),
data_theme = user . theme ,
),
),
)
async def render_welcome_page () -> None :
page = UserContext (
welcome_page ( "Welcome back " ),
username = "John" ,
theme = "dark" ,
)
result = await HTMY (). render ( page )
print ( result )
if __name__ == "__main__" :
asyncio . run ( render_welcome_page ())当然,您可以依靠内置上下文相关的实用程序(例如ContextAware或WithContext类),以方便且打字的上下文使用较少的样板代码。
如前所述,内置Formatter类别负责标记属性名称和值格式。您可以仅通过扩展此类或在其实例中添加新规则,然后将自定义实例添加到上下文中,直接在HTMY或HTMY.render()或上下文提供商组件中添加自定义实例,然后将自定义实例添加到上下文中。
这些是默认标签属性格式规则:
_ > - ),在这种情况下,删除了前导和尾随下划线并保留其余的属性名称。例如, data_theme="dark"将转换为data-theme="dark" ,但是_data_theme="dark"最终将以data_theme =“ dark”中的文本中的data_theme="dark" 。更重要的是class_="text-danger" , _class="text-danger" , _class__="text-danger"均转换为class="text-danger" , _for="my-input"或for_="my_input"将变成for="my-input" 。bool属性值转换为字符串( "true"和"false" )。XBool.true属性值将转换为一个空字符串,然后跳过XBool.false值(仅呈现属性名称)。date和datetime属性值转换为ISO字符串。如果您希望应用程序优雅地失败(例如显示错误消息),而不是增加HTTP错误,则该ErrorBoundary组件很有用。
错误边界包裹组件组件子树。当渲染器遇到ErrorBoundary组件时,它将尝试渲染其包裹的内容。如果在ErrorBoundary的子树中的任何时刻渲染失败,并且渲染器将自动归还给您分配给ErrorBoundary的fallback属性的组件。
可选地,您可以定义错误边界可以处理哪些错误,从而使您对错误处理进行良好的控制。
通常,如果必须等待一些异步调用,则应是异步。
如果组件执行潜在的“长期运行”同步呼叫,则强烈建议将呼叫的呼叫委托给工人线程等待它(从而使组件异步)。例如,可以使用anyio的to_thread实用程序, starlette 's(或fastapi 's) run_in_threadpool()等。这里的目标是避免阻止Asyncio事件循环,因为这可能导致性能问题。
在所有其他情况下,最好使用同步组件。
fastapi:
在频谱的一端,有一个完整的应用程序框架将服务器(Python)和客户端(JavaScript)应用程序与整个状态管理和同步组合为单个Python(在某些情况下是其他JavaScript)软件包。一些最受欢迎的例子是:Reflex,nicegui,reactpy和fastui。
这些框架的主要好处是快速应用程序原型制作和非常方便的开发人员体验(至少只要您留在框架的内置功能集中)。为此,它们非常有用(从组件到前端工具和状态管理),基础工程非常复杂,部署和缩放可能很难或昂贵,并且很难迁移。即使使用这些警告,它们也可以成为内部工具和应用程序原型制作的绝佳选择。
光谱的另一端 - 普通渲染引擎 - 由Jinja模板引擎主导,这是一个安全的选择,并且将持续很长时间。 Jinja的主要缺点是缺乏良好的IDE支持,完全缺乏静态代码分析支持以及(主观)丑陋的语法。
然后,有一些工具可以针对中间地,通常是通过提供完整应用程序框架的大多数好处和缺点,而离开州管理,客户服务器通信和动态UI更新,供用户解决,通常具有一定级别的HTMX支持。该组包括诸如Fasthtml和Ludic之类的图书馆。
htmy的主要目的是成为异步,纯净的Python渲染引擎,它尽可能简单,可维护和可定制,同时仍为(方便)提供所有构建块,以创建复杂且可维护的应用程序。
该图书馆旨在将其依赖性最小化。目前需要以下依赖关系:
anyio :用于异步文件操作和网络。async-lru :用于异步缓存。markdown :用于MARKDOWN解析。 使用ruff进行覆盖和格式,用于静态代码分析的mypy ,以及用于测试的pytest 。
该文档是使用mkdocs-material和mkdocstrings构建的。
欢迎所有贡献,包括更多文档,示例,代码和测试。甚至问题。
该包在MIT许可证的条件下开源。