رمز المصدر : https://github.com/volfpeter/htmy
الوثائق والأمثلة : https://volfpeter.github.io/htmy
htmyAsync ، محرك عرض نقي Python .
ErrorBoundary لمعالجة الأخطاء الرشيقة.الحزمة متوفرة على PYPI ويمكن تثبيتها مع:
$ pip install htmy المكتبة بأكملها-من محرك التقديم نفسه إلى المكونات المدمجة-مبنية على عدد قليل من البروتوكولات البسيطة وحفنة من فئات المنفعة البسيطة. هذا يعني أنه يمكنك بسهولة تخصيص أو تمديد أو استبدال كل شيء في المكتبة بشكل أساسي. نعم ، حتى محرك التقديم. الأجزاء المتبقية سوف تستمر في العمل كما هو متوقع.
أيضًا ، لا تعتمد المكتبة على ميزات Python المتقدمة مثل metaclasses أو الواصفات. لا توجد أيضًا فئات قاعدة معقدة وما شابه. حتى المهندس المبتدئين يمكن أن يفهم وتطوير وتصحيح تطبيق تم تصميمه مع htmy .
كل فئة مع Sync أو Async htmy(context: Context) -> Component هي مكون htmy (من الناحية الفنية HTMYComponentType ). السلاسل هي أيضًا مكونات ، بالإضافة إلى قوائم أو tuples من HTMYComponentType أو كائنات السلسلة.
يتيح استخدام اسم الطريقة هذا تحويل أي من كائنات عملك (من TypedDicts S أو pydantic إلى فئات 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 أيضًا ديكور @component يمكن استخدامه على Sync أو Async 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 : MarkdownParser الأداة المساعدة ومكون MD للتحميل ، التحليل ، التحويل ، وتقديم محتوى تخفيض.i18n : الأدوات المساعدة لـ Async ، التدويل القائم على 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 ) بالكامل من قبل العارض. إضافة مكونات موفر Context (أي فئة مع Sync أو Async htmy_context() -> Context ) إضافة بيانات جديدة إلى السياق لجعلها متاحة للمكونات في الشجرة الفرعية الخاصة بهم ، ويمكن للمكونات ببساطة أخذ ما يحتاجون إليه من السياق.
لا يوجد أي قيود على ما يمكن أن يكون في السياق ، ويمكن استخدامه لأي شيء يحتاجه التطبيق ، على سبيل المثال جعل المستخدم الحالي أو تفضيلات واجهة المستخدم أو الموضوعات أو التنسيقات المتاحة للمكونات. في الواقع ، تحصل المكونات المدمجة على 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. يكون المكون ErrorBoundary مفيدًا إذا كنت تريد أن يفشل التطبيق بأمان (على سبيل المثال ، عرض رسالة خطأ) بدلاً من رفع خطأ HTTP.
يلف حدود الخطأ شجرة فرعية مكون. عندما يواجه العارض مكونًا ErrorBoundary ، سيحاول تقديم محتوى ملفوف. إذا فشل العرض باستثناء في أي وقت من الشجرة الفرعية لـ ErrorBoundary ، فسوف يعود العارض تلقائيًا إلى المكون الذي قمت بتعيينه في خاصية fallback الخاصة بـ ErrorBoundary .
اختياريا ، يمكنك تحديد الأخطاء التي يمكن أن تتعامل معها حدود الخطأ ، مما يتيح لك التحكم الدقيق في معالجة الأخطاء.
بشكل عام ، يجب أن يكون المكون غير متزامن إذا كان يجب أن ينتظر بعض الدعوة غير المتزامنة في الداخل.
إذا قام أحد المكونات بتنفيذ مكالمة متزامنة "محتملة طويلة الأجل" ، فمن المستحسن بشدة تفويض هذا الاتصال إلى مؤشر ترابط العامل في انتظاره (مما يجعل المكون غير متزامن). يمكن القيام بذلك على سبيل المثال باستخدام الأداة المساعدة to_thread ، starlette anyio أو fastapi ) run_in_threadpool() ، وهكذا. الهدف هنا هو تجنب منع حلقة حدث Asyncio ، لأن ذلك يمكن أن يؤدي إلى مشكلات في الأداء.
في جميع الحالات الأخرى ، من الأفضل استخدام مكونات المزامنة.
fastapi:
في أحد طرفي الطيف ، هناك أطر التطبيق الكاملة التي تجمع بين تطبيقات الخادم (Python) وتطبيقات العميل (JavaScript) مع إدارة الحالة بأكملها وتزامنها في حزمة واحدة من Python (في بعض الحالات حزمة JavaScript). بعض الأمثلة الأكثر شعبية هي: Reflex و NiceGui و Reactpy و Fastui.
الفائدة الرئيسية لهذه الأطر هي النماذج الأولية للتطبيق السريع وتجربة مطور مريحة للغاية (على الأقل طالما أنك بقيت ضمن مجموعة الميزات المدمجة من الإطار). في مقابل ذلك ، فهي مصممة للغاية (من المكونات إلى الأدوات الأمامية وإدارة الدولة) ، والهندسة الأساسية معقدة للغاية ، ويمكن أن يكون النشر والتوسيع أمرًا صعبًا أو مكلفًا ، وقد يكون من الصعب الهجرة بعيدًا. حتى مع هذه التحذيرات ، يمكن أن تكون اختيارًا جيدًا للغاية للأدوات الداخلية والنماذج الأولية للتطبيق.
يهيمن على الطرف الآخر من الطيف - محركات التقديم العادي - محرك جينجا ترامب ، وهو خيار آمن كما كان وسيكون موجودًا لفترة طويلة. العيوب الرئيسية مع Jinja هي الافتقار إلى دعم IDE الجيد ، والافتقار التام لدعم تحليل الكود الثابت ، وبناء الجملة القبيحة (ذاتية).
ثم هناك أدوات تهدف إلى الوسط ، عادةً من خلال توفير معظم الفوائد والعيوب في أطر التطبيق الكاملة مع ترك إدارة الدولة ، واتصالات خادم العميل ، وتحديثات واجهة المستخدم الديناميكية التي يحلها المستخدم ، وغالبًا ما يكون ذلك بمستوى معين من دعم HTMX. تتضمن هذه المجموعة مكتبات مثل Fashtml و Ludic.
الهدف الأساسي لـ htmy هو أن يكون محركًا غير متزامن ، وهو أمر بسيط ، وهو بسيط ، قابل للصيانة ، وقابل للتخصيص ، مع الاستمرار في توفير جميع لبنات البناء لتطبيقات معقدة وقابلة للصيانة.
تهدف المكتبة إلى الحد الأدنى من تبعياتها. حاليا التبعيات التالية مطلوبة:
anyio : لعمليات ملفات Async والشبكات.async-lru : للتخزين المؤقت Async.markdown : لتحليل تخفيضات. استخدم ruff للاندماج والتنسيق ، mypy لتحليل التعليمات البرمجية الثابتة ، و pytest للاختبار.
تم تصميم الوثائق باستخدام mkdocs-material و mkdocstrings .
جميع المساهمات موضع ترحيب ، بما في ذلك المزيد من الوثائق والأمثلة والرمز والاختبارات. حتى الأسئلة.
الحزمة مفتوحة المصدر في ظل ظروف ترخيص معهد ماساتشوستس للتكنولوجيا.