PyPositron Docs

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 as document, 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 object
  • document: The Document object for DOM manipulation
  • exposed: Access to exposed functions
  • import_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:

  1. Existing component libraries that require JavaScript
  2. Complex animations that CSS can't handle
  3. 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

  1. HTML loads and renders
  2. <py> tags execute (inline first, then src files)
  3. main() function runs (from backend/main.py)
  4. 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.