源代碼: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許可證的條件下開源。