LayoutML - a library for creating HTML pages using Python
Project description
LayoutML (Layout Markup Library) is a library that allows you to describe the structure of web pages directly through code, turning Python into a markup language for web interfaces.
Key Features
- Creating HTML elements through Python classes
- Defining CSS styles programmatically
- Component-based approach (reusable layout blocks)
- Readable and declarative syntax
- Generating clean HTML/CSS without unnecessary code
- Easy integration with FastAPI, Flask, and Django
- Lightweight with no external dependencies
Who It’s For
- Python developers who don’t want to write HTML manually
- Backend developers (FastAPI / Django / Flask)
- Educational projects
- Generating HTML reports and interfaces
- Building custom web frameworks on top of LayoutML
Contents
Core Classes
- CSSBase - Base class for working with CSS styles
- CSSInline - Class for working with inline styles
- CSSSelectors - Class for managing CSS selectors
Base Elements
- BaseElement - Base class for all HTML elements
- HTMLElement - Class for working with HTML attributes
Semantic Elements
- Header - Semantic header element
<header> - Main - Semantic main content element
<main> - Footer - Semantic footer element
<footer> - Nav - Semantic navigation element
<nav> - Section - Semantic section element
<section> - Article - Semantic article element
<article> - Aside - Semantic sidebar element
<aside>
Text Elements
- Paragraph - Paragraph element
<p> - Span - Inline container
<span> - Anchor - Link element
<a> - Heading - Heading elements
<h1>-<h6> - ListElement - Base class for lists
- UnorderedList - Unordered list
<ul> - OrderedList - Ordered list
<ol>
Media Elements
- Image - Image element
<img>
Form Elements
- Form - Base class for form elements
- FormElement - Form input element
<input> - Input - Specialized input element
- Label - Label element
<label> - Button - Button element
<button> - Select - Dropdown list element
<select> - Textarea - Multiline text field
<textarea>
Layout
- Layout - Base class for layouts (Flexbox)
- HorizontalLayout - Horizontal layout
- VerticalLayout - Vertical layout
Document Structure
Routing
- Router - URL routing class
Application
- LayoutML - Main application class
Quick Start
Installation
pip install layoutml
Usage Examples
This section contains examples of building web applications with LayoutML. You’ll learn how to create pages, add elements, and run the server.
Basic Launch
The easiest way to run a LayoutML application:
from layoutml import LayoutML, Page
from layoutml.elements import Button, Label
from layoutml.layout import VerticalLayout
class BasePage(Page):
def __init__(self, object_name=None, doctype="html", title="LayoutML", lang="ru", **kwargs):
super().__init__(object_name=object_name, doctype=doctype, title=title, lang=lang, **kwargs)
self.head.set_icon("ico/logo.ico")
# Creating application
app = LayoutML()
# Creating page
main_page = BasePage(title="Home")
# Creating elements
label = Label(text="Hello World!")
button = Button(text="Click me", onclick="alert('Hello!')")
# Creating layout
v_layout = VerticalLayout(object_name="v_layout")
v_layout.add_element(label)
v_layout.add_element(button)
# Adding elements to page
main_page.body.add_element(v_layout)
# Registering page
app.include_page(main_page)
# Defining route
@app.route("/")
def home():
page = main_page.copy()
return page
# Starting application
if __name__ == "__main__":
app.start(host="localhost", port=3700)
After запуск, the server will be available at http://localhost:3700
Running with Uvicorn from Command Line
You can run the application using Uvicorn from the terminal:
pip install uvicorn
uvicorn main:app --host localhost --port 3700 --reload
Where main is your Python filename, and app is the name of your LayoutML application instance.
Running Uvicorn from Python Code
You can also launch Uvicorn directly from a Python script:
if __name__ == "__main__":
uvicorn.run(app, host="localhost", port=3700)
Creating Multiple Routes
Example application with multiple pages:
from layoutml import LayoutML, Page
from layoutml.elements import Anchor, Button, Label, Paragraph
from layoutml.layout import VerticalLayout
class BasePage(Page):
def __init__(self, object_name=None, doctype="html", title="LayoutML", lang="ru", **kwargs):
super().__init__(object_name=object_name, doctype=doctype, title=title, lang=lang, **kwargs)
self.head.set_icon("ico/logo.ico")
def get_main_page() -> Page:
# Creating page
main_page = BasePage(title="Home")
# Creating elements
label = Label(text="Hello World!")
button = Button(text="Click me", onclick="alert('Hello!')")
# Creating layout
v_layout = VerticalLayout(object_name="v_layout")
v_layout.add_element(label)
v_layout.add_element(button)
# Adding elements to page
main_page.add_element(v_layout)
return main_page
def get_about_page() -> Page:
# Creating page
page = BasePage(title="About Us")
# Creating elements
paragraph = Paragraph(text="We build web applications using LayoutML")
back_link = Anchor(href="/", text="Back to Home")
# Adding elements to page
page.add_element(paragraph)
page.add_element(back_link)
return page
# Creating application
app = LayoutML()
# Registering pages
main_page = get_main_page()
about_page = get_about_page()
app.include_page(main_page)
app.include_page(about_page)
# Defining routes
@app.route("/")
def home():
page = main_page.copy()
return page
@app.route("/about")
def about():
page = about_page.copy()
return page
# Starting application
if __name__ == "__main__":
app.start(host="localhost", port=3700)
Using Route Parameters
You can create dynamic pages with URL parameters:
from layoutml import LayoutML, Page
from layoutml.elements import Button, Label
from layoutml.layout import VerticalLayout
def get_echo_page() -> Page:
# Creating page
main_page = Page(title="Home")
# Creating elements
label = Label(text="Hello World!")
button = Button(object_name="button", text="Click me", onclick="alert('Hello!')")
# Creating layout
v_layout = VerticalLayout(object_name="v_layout")
v_layout.add_element(label)
v_layout.add_element(button)
main_page.add_element(v_layout)
return main_page
# Creating application
app = LayoutML()
# Registering page
username_page = get_echo_page()
app.include_page(username_page)
# Defining route
@app.route("/echo")
def echo(username: str):
page = username_page.copy()
button: Button = page.get_element("v_layout").get_element("button")
button.text += f" {username}"
return page
# Starting application
if __name__ == "__main__":
app.start(host="localhost", port=3700)
Adding CSS Styles
Example page with custom styles:
from layoutml import LayoutML, Page
from layoutml.elements import Paragraph, Button
def get_page() -> Page:
page = Page(object_name="main_page", title="Styled Page")
paragraph = Paragraph(text="This text is styled with CSS", class_="highlight-text")
button = Button(
text="Stylish Button",
class_="custom-button",
style="padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 5px;",
)
# Adding CSS styles via head object
page.head.selectors_styles.add_selector(".main-header")\
.set_background_color("#f8f9fa")\
.set_padding("20px")\
.set_text_align("center")
page.head.selectors_styles.add_selector(".highlight-text")\
.set_color("#007bff")\
.set_font_size("18px")\
.set_font_weight("bold")
page.head.selectors_styles.add_selector(".custom-button:hover")\
.set_background_color("#0056b3")\
.set_transform("scale(1.05)")
page.body.add_element(paragraph)
page.body.add_element(button)
return page
app = LayoutML()
main_page = get_page()
app.include_page(main_page)
@app.route("/")
def styled_page():
page = main_page.copy()
return page
if __name__ == "__main__":
app.start()
Using Layouts
Creating a page with horizontal and vertical layouts:
from layoutml import LayoutML, Page
from layoutml.elements import Paragraph, Button
from layoutml.layout import HorizontalLayout, VerticalLayout
def get_page() -> Page:
page = Page(object_name="main_page", title="Layout Example")
# Vertical layout for the whole page
main_layout = VerticalLayout(object_name="mainLayout")
main_layout.object_styles.set_gap("20px").set_padding("20px")
# Horizontal layout for navigation
nav_layout = HorizontalLayout(object_name="navLayout")
nav_layout.object_styles.set_justify_content("space-between")
nav_layout.add_element(Button(text="Home"))
nav_layout.add_element(Button(text="About Us"))
nav_layout.add_element(Button(text="Contacts"))
# Horizontal layout for cards
cards_layout = HorizontalLayout(object_name="cardsLayout")
cards_layout.object_styles.set_gap("20px").set_justify_content("center")
for i in range(3):
card = VerticalLayout(object_name=f"card{i}")
card.object_styles.set_border("1px solid #ddd").set_padding("15px").set_border_radius("8px").set_width("200px")
card.add_element(Paragraph(text=f"Card {i+1}"))
card.add_element(Button(text="Learn More"))
cards_layout.add_element(card)
main_layout.add_elements(nav_layout, cards_layout)
page.body.add_element(main_layout)
return page
app = LayoutML()
main_page = get_page()
app.include_page(main_page)
@app.route("/")
def layout_example():
page = main_page.copy()
return page
if __name__ == "__main__":
app.start()
Form Handling
Example of creating a page with a form and handling data:
from layoutml import LayoutML, Page
from layoutml.elements import Input, Button, Label, Paragraph
from layoutml.layout import VerticalLayout, HorizontalLayout
def get_form_page():
page = Page(object_name="form_page", title="Contacts")
page.body.add_element(Paragraph(text="Contact Us"))
# Creating form
h_layout_name = HorizontalLayout(object_name="h_layout_name")
h_layout_email = HorizontalLayout(object_name="h_layout_email")
# Name field
h_layout_name.add_element(Label(for_id="name", text="Name:"))
h_layout_name.add_element(Input(id="name", name="name", required=True))
# Email field
h_layout_email.add_element(Label(for_id="email_label", text="Email:"))
h_layout_email.add_element(Input(type="email", id="email", name="email", required=True))
# Submit button
button = Button(text="Submit", type="submit")
v_layout = VerticalLayout(object_name="v_layout")
v_layout.add_elements(h_layout_name, h_layout_email, button)
page.body.add_element(v_layout)
# Disabling automatic CSS file rendering for page
page.render_css_file = False
page.add_stylesheet(href="styles/form_page_styles.css")
return page
/* form_page_styles.css */
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body.Body {
font-family: "Inter", sans-serif;
background: #f8fafc;
color: #1e293b;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.v_layout {
background: #ffffff;
width: 100%;
max-width: 480px;
padding: 40px;
border-radius: 20px;
box-shadow: 0 10px 40px rgba(15, 23, 42, 0.08);
display: flex;
flex-direction: column;
gap: 24px;
border: 1px solid rgba(226, 232, 240, 0.8);
}
.ParagraphElement {
position: absolute;
top: 80px;
font-size: 2rem;
font-weight: 600;
color: #0f172a;
letter-spacing: -0.5px;
}
.h_layout_name,
.h_layout_email {
display: flex;
flex-direction: column;
gap: 8px;
}
.LabelElement {
font-size: 0.95rem;
font-weight: 500;
color: #475569;
}
.InputElement {
width: 100%;
padding: 14px 16px;
border: 1.5px solid #e2e8f0;
border-radius: 14px;
font-size: 1rem;
background: #f8fafc;
transition: all 0.25s ease;
outline: none;
}
.InputElement:focus {
border-color: #6366f1;
background: #ffffff;
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.12);
}
.ButtonElement {
margin-top: 10px;
padding: 15px;
border: none;
border-radius: 14px;
background: #6366f1;
color: white;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.25s ease;
}
.ButtonElement:hover {
background: #4f46e5;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.25);
}
.ButtonElement:active {
transform: translateY(0);
}
@media (max-width: 600px) {
.v_layout {
padding: 28px;
}
.ParagraphElement {
font-size: 1.6rem;
top: 40px;
}
}
Complete Usage Example
from layoutml import LayoutML, Page, Request, Response, JSONResponse
# Creating application
app = LayoutML(styles_dirname="assets/css")
# Creating base page
class BasePage(Page):
def __init__(self, object_name: str, title="LayoutML", **kwargs):
super().__init__(object_name=object_name, title=title, **kwargs)
self.head.set_icon("static/logo.ico") # Setting icon
# Creating and registering page
main_page = BasePage(object_name="main_page", title="Home")
main_page.body.add_content("<h1>Welcome!</h1>")
app.include_page(main_page)
# HTML route
@app.route("/")
async def home(request: Request, response: Response):
page = main_page.copy()
page.body.add_content("<p>This is the home page</p>")
return page
# API route with JSON response
@app.route("/api/data")
async def get_data(request: Request, response: Response):
return JSONResponse(content={"status": "ok", "data": [1, 2, 3]})
# Route with parameters
@app.route("/user")
async def user_profile(
request: Request,
response: Response,
user_id: int,
name: str = "Guest",
):
page = main_page.copy()
page.title = f"{name}'s Profile"
page.body.add_content(f"<h1>User: {name} (ID: {user_id})</h1>")
return page
# Custom 404 page
custom_404 = BasePage(object_name="error_page", title="Page Not Found")
custom_404.body.add_content("""
<div style="text-align: center; padding: 50px;">
<h1>404</h1>
<p>Page not found</p>
<a href="/">Back to Home</a>
</div>
""")
app.set_error_page(custom_404)
if __name__ == "__main__":
app.print_routes() # Viewing routes
app.start(host="localhost", port=3700)
Development Tips
- Development mode: Use the
--reloadflag when running through Uvicorn for automatic reload on code changes. - Debugging: You can print route information using the
print_routes()method:
app.print_routes()
- Code structuring: For large applications, it is recommended to split routes into modules:
# routes.py
from layoutml import LayoutML
app = LayoutML()
# Import routes from other modules
from .home_routes import home_router
from .api_routes import api_router
app.include_router(home_router)
app.include_router(api_router, prefix="/api")
- Async handlers: LayoutML supports asynchronous route handlers:
@app.route("/async-data")
async def async_data():
data = await fetch_data_from_db()
page = Page(title="Data")
page.body.add_element(Paragraph(text=str(data)))
return page
Best Practices
1. Use page copying
# Always copy page inside handler
@app.route("/profile")
def profile_handler(request: Request, response: Response):
page = base_page.copy() # ✅
return page
2. Register all pages
# Register all used pages
app.include_page(home_page)
app.include_page(about_page)
app.include_page(contact_page)
3. Use a base page class to set icons for all pages
class AppPage(Page):
def __init__(self, title="App", **kwargs):
super().__init__(title=title, **kwargs)
self.head.set_icon("/static/favicon.ico")
self.head.add_stylesheet("/static/app.css")
4. Handle errors
@app.route("/protected")
async def protected(request: Request, response: Response):
if not request.headers.get("Authorization"):
raise HTTPException(status_code=401, detail="Unauthorized")
return page.copy()
Project Status
LayoutML is under active development.
📄 License
Feedback
I’m always happy to receive your feedback and suggestions for improving LayoutML. Please leave your comments.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file layoutml-1.0.7.tar.gz.
File metadata
- Download URL: layoutml-1.0.7.tar.gz
- Upload date:
- Size: 46.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17757de6ad8cb371961424da6aaa2b7f89a21a2fdba80d531d668b00aa894af5
|
|
| MD5 |
d51f3a36f8581ba30f2f2b6013bb9f72
|
|
| BLAKE2b-256 |
e00a97431dd2131954eb692fd6471e3466c21d9fc6a869de3cbe3a232b80c5fd
|
File details
Details for the file layoutml-1.0.7-py3-none-any.whl.
File metadata
- Download URL: layoutml-1.0.7-py3-none-any.whl
- Upload date:
- Size: 55.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f38e2986183d195940fb853abfd1be86e1d8bab68876fdd12300fa0277c74ca2
|
|
| MD5 |
8b7714fbe469024899d66378a02c7997
|
|
| BLAKE2b-256 |
b4f0fbff50dd9e502186d72b1f0e0c4436cc918879a9871c5bb7ccfa22af7b35
|