ソースコード:https://github.com/volfpeter/htmy
ドキュメントと例:https://volfpeter.github.io/htmy
htmyasync 、純粋なパイソンレンダリングエンジン。
ErrorBoundaryコンポーネントは、優雅なエラー処理のために。パッケージはPYPIで利用でき、以下でインストールできます。
$ pip install htmy レンダリングエンジン自体から組み込みコンポーネントまで、ライブラリ全体は、いくつかのシンプルなプロトコルと少数のシンプルなユーティリティクラスを中心に構築されています。これは、基本的にすべてをライブラリ内のすべてのものを簡単にカスタマイズ、拡張、または交換できることを意味します。はい、レンダリングエンジンでさえ。残りの部品は、予想どおりに機能し続けます。
また、ライブラリはメタクラスや記述子などの高度なPython機能に依存していません。複雑な基本クラスもありません。ジュニアエンジニアでさえ、 htmyで構築されたアプリケーションを理解、開発、デバッグすることができました。
同期または非同期htmy(context: Context) -> Componentメソッドを持つすべてのクラスは、 htmyコンポーネント(技術的にはHTMYComponentType )です。文字列もコンポーネントであり、 HTMYComponentTypeまたはstringオブジェクトのリストまたはタプルです。
このメソッド名を使用すると、他のツールとの名前の衝突を恐れることなく、ビジネスオブジェクト( TypedDicts Sまたはpydantic ModelsからORMクラス)のコンポーネントへの変換が可能になります。
ASYNCサポートにより、コンポーネントでデータをロードしたり、ASYNCビジネスロジックを実行したりすることができます。これにより、場合によっては書く必要があるボイラープレートの量を減らすことができ、また、レンダリングおよび非レンダリングロジックをあらゆる方法で分割する自由を提供します。
例:
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 、同期または非同期my_component(props: MyProps, context: Context) -> Component propsで使用できる@componentデコレータを提供します。
上記と同じ例ですが、関数コンポーネントを使用してください。
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 :マークダウンコンテンツのロード、解析、変換、およびレンダリング用のMarkdownParserユーティリティとMDコンポーネント。i18n :JSONベースの国際化、ASYNCのユーティリティ。BaseTag 、 TagWithProps 、 Tag 、 WildcardTag :カスタムXMLタグのベースクラス。ErrorBoundary 、 Fragment 、 SafeStr 、 WithContext :エラー処理、コンポーネントラッパー、コンテキストプロバイダー、およびフォーマットのユーティリティ。Snippet :ファイルシステムからドキュメントスニペットをロードおよびカスタマイズするためのユーティリティクラス。etree.ETreeConverter :XMLをカスタムHTMYコンポーネントをサポートしてコンポーネントツリーに変換するユーティリティ。htmy.HTMYは、ライブラリの組み込みのデフォルトレンダラーです。
FastapiのようなAsync Webフレームワークでライブラリを使用している場合、すでに非同期環境にいるので、コンポーネントをこのようにレンダリングすることができます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 )は、レンダラーによって完全に管理されています。コンテキストプロバイダーコンポーネント(同期またはasyncを備えたクラス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"として終了します。さらに重要なのは、 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の(またはfastapi 's) run_in_threadpool()などで実行できます。ここでの目標は、パフォーマンスの問題につながる可能性があるため、Asyncioイベントループのブロックを避けることです。
他のすべての場合、同期コンポーネントを使用することをお勧めします。
FASTAPI:
スペクトルの一端には、サーバー(Python)とクライアント(JavaScript)アプリケーションと州管理と同期を単一のPython(場合によっては追加のJavaScript)パッケージに同期する完全なアプリケーションフレームワークがあります。最も人気のある例のいくつかは、反射、NiceGui、Reactpy、およびFastUiです。
これらのフレームワークの主な利点は、迅速なアプリケーションのプロトタイピングと非常に便利な開発者エクスペリエンスです(少なくともフレームワークの組み込み機能セット内にとどまる限り)。それと引き換えに、それらは非常に意見があり(コンポーネントからフロントエンドのツーリング、および州の管理まで)、基礎となるエンジニアリングは非常に複雑であり、展開とスケーリングは硬くても費用がかかる可能性があり、離れて移動するのは難しい場合があります。これらの警告があっても、内部ツールとアプリケーションのプロトタイピングには非常に良い選択肢になる可能性があります。
スペクトルのもう一方の端 - プレーンレンダリングエンジン - は、ジンジャテンプレートエンジンに支配されています。ジンジャの主な欠点は、優れたIDEサポートの欠如、静的コード分析サポートの完全な欠如、および(主観的に)醜い構文です。
次に、一般的なアプリケーションフレームワークのほとんどの利点と欠点を提供しながら、州の管理、クライアントサーバー通信、およびユーザーが解決するためのダイナミックUIアップデートを、ある程度のHTMXサポートを備えたダイナミックUIアップデートを提供することにより、中央を目指すツールがあります。このグループには、FastthtmlやLudicなどのライブラリが含まれています。
htmyの主な目的は、可能な限りシンプルで保守可能で、カスタマイズ可能な非同期の純粋なパイソンレンダリングエンジンであることです。一方、複雑で保守可能なアプリケーションを作成するためのすべてのビルディングブロックを提供します。
ライブラリは、その依存関係を最小限に抑えることを目指しています。現在、次の依存関係が必要です。
anyio :Asyncファイルの操作とネットワーク用。async-lru :asyncキャッシング用。markdown :マークダウンの解析用。 糸くずとフォーマットにはruffを使用し、静的コード分析にはmypy 、およびテストにはpytest使用します。
このドキュメントはmkdocs-materialおよびmkdocstringsで構築されています。
より多くのドキュメント、例、コード、テストなど、すべての貢献を歓迎します。質問さえ。
このパッケージは、MITライセンスの条件の下でオープンソーリングされています。