Kode Sumber : https://github.com/volfpeter/htmy
Dokumentasi dan Contoh : https://volfpeter.github.io/htmy
htmyAsync , mesin rendering -python murni .
ErrorBoundary yang mudah digunakan untuk penanganan kesalahan yang anggun.Paket tersedia di PYPI dan dapat diinstal dengan:
$ pip install htmy Seluruh perpustakaan-dari mesin rendering itu sendiri hingga komponen bawaan-dibangun di sekitar beberapa protokol sederhana dan beberapa kelas utilitas sederhana. Ini berarti Anda dapat dengan mudah menyesuaikan, memperluas, atau mengganti pada dasarnya semuanya di perpustakaan. Ya, bahkan mesin rendering. Bagian yang tersisa akan terus bekerja seperti yang diharapkan.
Juga, perpustakaan tidak mengandalkan fitur Python canggih seperti metaklasses atau deskriptor. Juga tidak ada kelas dasar yang kompleks dan sejenisnya. Bahkan seorang insinyur junior dapat memahami, mengembangkan, dan men -debug aplikasi yang dibangun dengan htmy .
Setiap kelas dengan sinkronisasi atau async htmy(context: Context) -> Component adalah komponen htmy (secara teknis HTMYComponentType ). String juga merupakan komponen, serta daftar atau tupel objek HTMYComponentType atau string.
Menggunakan Metode ini Nama memungkinkan konversi salah satu objek bisnis Anda (dari TypedDicts S atau pydantic Model ke kelas ORM) menjadi komponen tanpa takut tabrakan nama dengan alat lain.
Dukungan async memungkinkan untuk memuat data atau menjalankan logika bisnis async tepat di komponen Anda. Ini dapat mengurangi jumlah pelat boiler yang perlu Anda tulis dalam beberapa kasus, dan juga memberi Anda kebebasan untuk membagi logika rendering dan non-render dengan cara apa pun yang Anda inginkan.
Contoh:
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 juga menyediakan dekorator @component yang dapat digunakan pada sinkronisasi atau async my_component(props: MyProps, context: Context) -> Component untuk mengubahnya menjadi komponen (melestarikan pengetikan props ).
Berikut adalah contoh yang sama seperti di atas, tetapi dengan komponen fungsi:
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 memiliki serangkaian utilitas dan komponen bawaan yang kaya untuk HTML dan kasus penggunaan lainnya:
html : Satu set lengkap tag HTML baseline.md : Utilitas MarkdownParser dan komponen MD untuk memuat, parsing, konversi, dan rendering konten penurunan harga.i18n : Utilitas untuk Async, internasionalisasi berbasis JSON.BaseTag , TagWithProps , Tag , WildcardTag : kelas dasar untuk tag XML khusus.ErrorBoundary , Fragment , SafeStr , WithContext : Utilities untuk Penanganan Kesalahan, Pembungkus Komponen, Penyedia Konteks, dan Pemformatan.Snippet : Kelas Utilitas untuk Memuat dan Menyesuaikan Cuplikan Dokumen dari Sistem File.etree.ETreeConverter : Utilitas yang mengkonversi XML ke pohon komponen dengan dukungan untuk komponen HTMY khusus. htmy.HTMY adalah renderer perpustakaan bawaan, default.
Jika Anda menggunakan perpustakaan dalam kerangka web async seperti Fastapi, maka Anda sudah berada di lingkungan async, sehingga Anda dapat membuat komponen seperti ini: await HTMY().render(my_root_component) .
Jika Anda mencoba menjalankan renderer di lingkungan sinkronisasi, seperti skrip lokal atau CLI, maka Anda harus pertama -tama harus membungkus renderer dalam tugas async dan menjalankan tugas itu dengan 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 ()) Seperti yang dapat Anda lihat dari contoh kode di atas, setiap komponen memiliki context: Context , yang belum kami gunakan sejauh ini. Konteks adalah cara untuk berbagi data dengan seluruh subtree komponen tanpa "pengeboran prop".
Konteks (secara teknis Mapping ) sepenuhnya dikelola oleh renderer. Komponen penyedia konteks (kelas apa pun dengan sinkronisasi atau async htmy_context() -> Context ) menambahkan data baru ke konteks untuk membuatnya tersedia untuk komponen di subtree mereka, dan komponen dapat dengan mudah mengambil apa yang mereka butuhkan dari konteks.
Tidak ada batasan pada apa yang bisa dalam konteks, dapat digunakan untuk apa pun yang dibutuhkan aplikasi, misalnya membuat pengguna saat ini, preferensi, tema, atau format yang tersedia untuk komponen. Bahkan, komponen bawaan mendapatkan Formatter mereka dari konteks jika berisi satu, untuk memungkinkan untuk menyesuaikan nama properti tag dan format nilai.
Berikut adalah contoh penyedia konteks dan implementasi konsumen:
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 ()) Anda tentu saja dapat mengandalkan utilitas terkait konteks bawaan seperti ContextAware atau dengan kelas WithContext untuk penggunaan konteks yang nyaman dan diketik dengan kode boilerplate yang lebih sedikit.
Seperti yang disebutkan sebelumnya, kelas Formatter bawaan bertanggung jawab untuk nama atribut tag dan pemformatan nilai. Anda dapat sepenuhnya mengganti atau memperluas perilaku pemformatan bawaan hanya dengan memperluas kelas ini atau menambahkan aturan baru ke instance dari itu, dan kemudian menambahkan instance kustom ke konteks, baik secara langsung di HTMY atau HTMY.render() , atau dalam komponen penyedia konteks.
Ini adalah aturan pemformatan atribut tag default:
_ -> - ) kecuali nama atribut dimulai atau diakhiri dengan garis bawah, dalam hal ini memimpin dan membuntuti garis bawah dilepas dan nama atribut lainnya dipertahankan. Misalnya data_theme="dark" dikonversi menjadi data-theme="dark" , tetapi _data_theme="dark" akan berakhir sebagai data_theme="dark" dalam teks yang diberikan. Lebih penting lagi class_="text-danger" , _class="text-danger" , _class__="text-danger" semuanya dikonversi ke class="text-danger" , dan _for="my-input" atau for_="my_input" akan menjadi for="my-input" .bool dikonversi ke string ( "true" dan "false" ).XBool.true dikonversi ke string kosong, dan nilai XBool.false dilewati (hanya nama atribut yang diberikan).date dan datetime dikonversi ke string ISO. Komponen ErrorBoundary bermanfaat jika Anda ingin aplikasi Anda gagal dengan anggun (misalnya menampilkan pesan kesalahan) alih -alih menaikkan kesalahan HTTP.
Batas kesalahan membungkus subtree komponen komponen. Ketika renderer menemukan komponen ErrorBoundary , ia akan mencoba membuat konten yang dibungkus. Jika rendering gagal dengan pengecualian pada titik mana pun di subtree ErrorBoundary , renderer akan secara otomatis kembali ke komponen yang Anda tetapkan ke properti fallback ErrorBoundary .
Secara opsional, Anda dapat menentukan kesalahan mana yang dapat ditangani oleh batas kesalahan, memberi Anda kontrol yang baik atas penanganan kesalahan.
Secara umum, komponen harus async jika harus menunggu beberapa panggilan async di dalam.
Jika komponen menjalankan panggilan sinkron yang berpotensi "berjalan lama", sangat disarankan untuk mendelegasikan panggilan itu ke utas pekerja yang menanti itu (sehingga membuat komponen async). Ini dapat dilakukan misalnya dengan utilitas to_thread anyio , starlette (atau fastapi 's) run_in_threadpool() , dan sebagainya. Tujuannya di sini adalah untuk menghindari memblokir loop acara Asyncio, karena itu dapat menyebabkan masalah kinerja.
Dalam semua kasus lain, yang terbaik adalah menggunakan komponen sinkronisasi.
Fastapi:
Di salah satu ujung spektrum, ada kerangka kerja aplikasi lengkap yang menggabungkan aplikasi server (python) dan klien (JavaScript) dengan seluruh manajemen negara dan sinkronisasi menjadi paket python tunggal (dalam beberapa kasus javascript tambahan). Beberapa contoh yang paling populer adalah: refleks, niceegui, reactpy, dan fastui.
Manfaat utama dari kerangka kerja ini adalah pembuatan prototipe aplikasi yang cepat dan pengalaman pengembang yang sangat nyaman (setidaknya selama Anda tetap berada dalam set fitur bawaan dari kerangka kerja). Sebagai imbalan atas hal itu, mereka sangat berpendapat (dari komponen hingga perkakas frontend dan manajemen negara), rekayasa yang mendasarinya sangat kompleks, penyebaran dan penskalaan bisa sulit atau mahal, dan mereka bisa sulit untuk bermigrasi. Bahkan dengan peringatan ini, mereka bisa menjadi pilihan yang sangat baik untuk alat internal dan prototipe aplikasi.
Ujung spektrum lainnya - mesin rendering polos - didominasi oleh mesin templating jinja, yang merupakan pilihan yang aman seperti yang telah dan akan ada untuk waktu yang lama. Kelemahan utama dengan jinja adalah kurangnya dukungan IDE yang baik, kurangnya dukungan analisis kode statis, dan sintaksis jelek (subyektif).
Lalu ada alat yang bertujuan untuk MiddleGround, biasanya dengan memberikan sebagian besar manfaat dan kelemahan dari kerangka kerja aplikasi lengkap sambil meninggalkan manajemen negara, komunikasi klien-server, dan pembaruan UI yang dinamis untuk dipecahkan pengguna, seringkali dengan beberapa tingkat dukungan HTMX. Grup ini mencakup perpustakaan seperti Fasthtml dan Ludic.
Tujuan utama dari htmy adalah untuk menjadi mesin rendering python murni , yang sesederhana , dapat dipelihara , dan dapat disesuaikan mungkin, sambil tetap menyediakan semua blok bangunan untuk (mudah) menciptakan aplikasi yang kompleks dan dapat dipelihara.
Perpustakaan bertujuan untuk meminimalkan ketergantungannya. Saat ini dependensi berikut diperlukan:
anyio : untuk operasi file async dan jaringan.async-lru : Untuk caching async.markdown : Untuk penguraian markdown. Gunakan ruff untuk serat dan format, mypy untuk analisis kode statis, dan pytest untuk pengujian.
Dokumentasi ini dibangun dengan mkdocs-material dan mkdocstrings .
Semua kontribusi dipersilakan, termasuk lebih banyak dokumentasi, contoh, kode, dan tes. Bahkan pertanyaan.
Paket ini bersumber terbuka di bawah ketentuan lisensi MIT.