Python and HTML Integration Guide
PyPositron provides multiple ways to integrate Python with your HTML interface. This guide explains the primary approach using backend/main.py
and the alternative inline <py>
tags for simple use cases.
Primary Approach: backend/main.py
The recommended and most powerful way to interact with your HTML interface is through your backend/main.py
file. This approach provides full Python capabilities and clean separation of logic.
Basic Example
frontend/index.html:
<!DOCTYPE html>
<html>
<head>
<title>Dynamic Content</title>
</head>
<body>
<h1>Welcome!</h1>
<button id="time-button">Get Current Time</button>
<div id="content"></div>
</body>
</html>
backend/main.py:
import py_positron as positron
import datetime
def main(ui: positron.PositronWindowWrapper):
# Get elements
button = ui.document.getElementById("time-button")
content_div = ui.document.getElementById("content")
def show_time():
now = datetime.datetime.now()
time_str = now.strftime("%Y-%m-%d %H:%M:%S")
content_div.innerHTML = f"<p>Current time: {time_str}</p>"
# Set up event handler
button.addEventListener("click", show_time)
positron.openUI("frontend/index.html", main, title="Time App")
Alternative: Inline <py>
Tags
<py>
tags are available for inline use and single-file projects, similar to how <script>
tags work in regular web development. They're perfect for simple scripts or when you want everything in one HTML file.
Basic Syntax
Inline Python Code
<py>
# Python code here
print("Hello from Python!")
button = document.getElementById("myButton")
button.innerText = "Updated from Python"
</py>
External Python Files
<py src="path/to/script.py"></py>
Key Differences from backend/main.py
- No
ui
parameter: Objects are available directly asdocument
,window
, etc. - Limited scope: Best for simple operations and single-file projects
- Execution timing: Runs after HTML is loaded, before main function
Available Objects in <py>
Tags
window
: The webview window objectdocument
: The Document object for DOM manipulationexposed
: Access to exposed functionsimport_module
: Function to import Python modules
Examples with backend/main.py (Recommended)
Example 1: Form Validation with File Access
frontend/index.html:
<!DOCTYPE html>
<html>
<head>
<title>User Registration</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<form id="userForm">
<div class="mb-3">
<input type="email" id="email" class="form-control" placeholder="Enter email">
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
<div id="message" class="mt-3"></div>
</div>
</body>
</html>
backend/main.py:
import py_positron as positron
import re
import json
import os
def main(ui: positron.PositronWindowWrapper):
form = ui.document.getElementById("userForm")
email_input = ui.document.getElementById("email")
message_div = ui.document.getElementById("message")
def validate_and_save():
email = email_input.value
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, email):
message_div.innerHTML = '<div class="alert alert-danger">Invalid email format</div>'
return
# Save to file (something <py> tags can't do securely)
users_file = "users.json"
users = []
if os.path.exists(users_file):
with open(users_file, 'r') as f:
users = json.load(f)
users.append({"email": email, "registered_at": "2025-06-28"})
with open(users_file, 'w') as f:
json.dump(users, f, indent=2)
message_div.innerHTML = '<div class="alert alert-success">Registration successful!</div>'
email_input.value = "" # Clear form
form.addEventListener("submit", validate_and_save)
positron.openUI("frontend/index.html", main, title="Registration App")
Example 2: Data Loading and Complex Logic
backend/main.py:
import py_positron as positron
import requests
import sqlite3
from datetime import datetime
def main(ui: positron.PositronWindowWrapper):
load_button = ui.document.getElementById("load-data")
data_container = ui.document.getElementById("data-container")
def load_and_process_data():
try:
# Complex operations that require full Python capabilities
# 1. Fetch data from API
response = requests.get("https://api.example.com/data")
api_data = response.json()
# 2. Store in database
conn = sqlite3.connect("app_data.db")
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS items
(id INTEGER PRIMARY KEY, name TEXT, value TEXT, created_at TEXT)
""")
# 3. Process and display
html_content = "<div class='row'>"
for item in api_data[:10]: # Limit to 10 items
cursor.execute(
"INSERT INTO items (name, value, created_at) VALUES (?, ?, ?)",
(item['name'], item['value'], datetime.now().isoformat())
) # This is only for prototyping, do not use in production
html_content += f"""
<div class='col-md-4 mb-3'>
<div class='card'>
<div class='card-body'>
<h5 class='card-title'>{item['name']}</h5>
<p class='card-text'>{item['value']}</p>
</div>
</div>
</div>
"""
html_content += "</div>"
conn.commit()
conn.close()
data_container.innerHTML = html_content
except Exception as e:
data_container.innerHTML = f'<div class="alert alert-danger">Error: {str(e)}</div>'
load_button.addEventListener("click", load_and_process_data)
Inline <py>
Tags for Simple Cases
For simple scripts or single-file projects, you can use <py>
tags directly in your HTML:
Example: Single-File Simple App
<!DOCTYPE html>
<html>
<head>
<title>Simple Calculator</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 p-8">
<div class="max-w-md mx-auto bg-white rounded-lg shadow-md p-6">
<h1 class="text-2xl font-bold mb-4">Simple Calculator</h1>
<input type="number" id="num1" placeholder="First number" class="w-full p-2 border rounded mb-2">
<input type="number" id="num2" placeholder="Second number" class="w-full p-2 border rounded mb-2">
<button id="add-btn" class="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600">
Add Numbers
</button>
<div id="result" class="mt-4 p-2 bg-gray-50 rounded"></div>
</div>
<py>
# Simple inline calculation - perfect for <py> tags
def calculate():
try:
num1 = float(document.getElementById("num1").value)
num2 = float(document.getElementById("num2").value)
result = num1 + num2
result_div = document.getElementById("result")
result_div.innerHTML = f"<strong>Result: {result}</strong>"
except ValueError:
result_div = document.getElementById("result")
result_div.innerHTML = "<span class='text-red-500'>Please enter valid numbers</span>"
# Set up event listener
add_button = document.getElementById("add-btn")
add_button.addEventListener("click", calculate)
</py>
</body>
</html>
External <py src>
Example
calculator.html:
<!DOCTYPE html>
<html>
<head>
<title>External Script Calculator</title>
</head>
<body>
<input type="number" id="a" placeholder="A">
<input type="number" id="b" placeholder="B">
<button id="calc">Calculate</button>
<div id="output"></div>
<py src="calc_logic.py"></py>
</body>
</html>
calc_logic.py:
def setup_calculator():
calc_button = document.getElementById("calc")
def perform_calculation():
a = float(document.getElementById("a").value or 0)
b = float(document.getElementById("b").value or 0)
result = a * b + (a + b) / 2
output = document.getElementById("output")
output.innerHTML = f"Complex calculation result: {result:.2f}"
calc_button.addEventListener("click", perform_calculation)
setup_calculator()
Framework Compatibility and Optional JavaScript
PyPositron provides everything you need through Python - JavaScript is optional and primarily used for framework compatibility.
CSS Frameworks (Recommended)
PyPositron works seamlessly with CSS frameworks:
<!DOCTYPE html>
<html>
<head>
<title>Pure CSS Framework Example</title>
<!-- CSS frameworks work perfectly with PyPositron -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div class="container mt-5">
<button id="action-btn" class="btn btn-primary">Python-Powered Button</button>
<div id="result" class="mt-3 p-3 bg-light rounded"></div>
</div>
</body>
</html>
backend/main.py:
import py_positron as positron
def main(ui):
button = ui.document.getElementById("action-btn")
result = ui.document.getElementById("result")
def handle_click():
# Pure Python - no JavaScript needed!
result.innerHTML = "<strong>Handled entirely by Python!</strong>"
button.innerText = "Clicked!"
button.addEventListener("click", handle_click)
positron.openUI("frontend/index.html", main)
Supported Frameworks
- ✅ CSS Frameworks: Bootstrap, Tailwind CSS, Bulma, Foundation, etc.
- ✅ CSS Libraries: Animate.css, Normalize.css, etc.
- ✅ JavaScript Frameworks: React, Vue, Angular (optional - Python usually better)
- ✅ JS Libraries: jQuery, Alpine.js (optional - Python usually better)
When You Might Use JavaScript (Optional)
JavaScript is optional but can be useful for:
- Existing component libraries that require JavaScript
- Complex animations that CSS can't handle
- Third-party widgets that only have JavaScript implementations
<!DOCTYPE html>
<html>
<head>
<title>Optional JavaScript Example</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<!-- Python handles the core logic -->
<button id="python-button" class="btn btn-primary">Python Action</button>
<!-- JavaScript only for framework compatibility -->
<button id="js-animation" class="btn btn-secondary">Optional Animation</button>
<div id="content" class="mt-3"></div>
</div>
<!-- Optional JavaScript for specific needs -->
<script>
// Only used for complex animations or third-party widgets
document.getElementById("js-animation").addEventListener("click", function() {
// Example: complex CSS animation that's easier in JS
this.style.transform = "rotate(360deg)";
this.style.transition = "transform 0.5s ease";
});
</script>
<!-- Python handles everything else -->
<py>
def handle_main_action():
# Python does the real work
content = document.getElementById("content")
content.innerHTML = """
<div class='alert alert-success'>
<strong>Python is doing all the heavy lifting!</strong>
<ul>
<li>DOM manipulation ✓</li>
<li>Event handling ✓</li>
<li>File operations ✓</li>
<li>Database access ✓</li>
<li>Complex logic ✓</li>
</ul>
</div>
"""
python_btn = document.getElementById("python-button")
python_btn.addEventListener("click", handle_main_action)
</py>
</body>
</html>
Key Differences
Feature | Python (backend/main.py or <py> ) |
JavaScript (Optional) |
---|---|---|
DOM Manipulation | ✅ Full support | ✅ Full support |
Event Handling | ✅ Full support | ✅ Full support |
File Operations | ✅ Full access | ❌ Security restrictions |
Database Access | ✅ Full access | ❌ No direct access |
System Operations | ✅ Full access | ❌ Sandboxed |
Complex Logic | ✅ Full Python stdlib | ✅ Limited capabilities |
Recommendation | Primary approach | Optional for compatibility |
When to Use Each Approach
Use backend/main.py
for:
- ✅ Production applications
- ✅ Complex logic and algorithms
- ✅ File operations and database access
- ✅ API calls and data processing
- ✅ Clean separation of concerns
- ✅ Error handling and logging
Use <py>
tags for:
- ✅ Quick prototypes and demos
- ✅ Single-file applications
- ✅ Simple DOM manipulations
- ✅ Learning and experimentation
- ✅ When you want everything in one HTML file
Use JavaScript for:
- ✅ Framework compatibility (when using JS-heavy libraries)
- ✅ Third-party widgets (that only have JS implementations)
- ✅ Complex CSS animations (that are easier in JS)
- ✅ Legacy web components (from existing codebases)
Note: JavaScript is optional - PyPositron provides all core functionality through Python.
Best Practices
1. Structure for Maintainability
Recommended project structure:
your_project/
├── backend/
│ └── main.py # Main Python logic
├── frontend/
│ ├── index.html # Clean HTML structure
│ ├── styles.css or index.css # Styling
│ └── script.js # Client-side JavaScript (if needed)
└── data/ # Data files, databases, etc.
2. Combining Approaches
# backend/main.py - Primary approach
def main(ui):
# Python handles all the core logic
data = process_complex_data()
# Pass data to frontend
ui.document.getElementById("data").innerText = str(data)
# Set up sophisticated event handlers
setup_advanced_features(ui)
# Optional <py> tags for simple initialization:
# <py>
# document.getElementById("loading").style.display = "none"
# </py>
# JavaScript only if needed for specific frameworks:
# <script>
# // Only for third-party widgets or specific animations
# initializeOptionalWidget();
# </script>
3. Error Handling
<py>
try:
# Simple operations in py tags
element = document.getElementById("target")
element.innerText = "Success"
except Exception as e:
print(f"Error in py tag: {e}")
# Fallback behavior
</py>
Execution Order and Timing
- HTML loads and renders
<py>
tags execute (inline first, thensrc
files)main()
function runs (from backend/main.py)- Event handlers respond to user interactions
This ensures that <py>
tags can set up the initial state, and your main function can handle the complex logic and user interactions.