Write responsive web apps in full python
—
Essential classes and patterns for building Lona web applications. The core framework provides the fundamental components needed to create interactive web applications entirely in Python, including application setup, view handling, request processing, and routing.
The main application class that serves as the entry point for Lona web applications, managing routes, middleware, templates, static files, and server configuration.
class App:
def __init__(self, script_path: str):
"""
Initialize a Lona application.
Args:
script_path (str): Path to the main script file (__file__)
"""
def route(self, raw_pattern: str | int, name: str = '', interactive: bool = True,
http_pass_through: bool = False, frontend_view=None):
"""
Decorator for registering view routes.
Args:
raw_pattern (str | int): URL pattern to match or MATCH_ALL constant
name (str): Optional route name for reverse URL generation
interactive (bool): Whether the route supports interactivity
http_pass_through (bool): Pass HTTP requests directly to view
frontend_view: Optional frontend view function
Returns:
Decorator function
"""
def middleware(self):
"""
Decorator for registering middleware functions.
Returns:
Decorator function
"""
def frontend_view(self):
"""
Decorator for registering frontend views.
Returns:
Decorator function
"""
def error_403_view(self):
"""
Decorator for registering 403 error views.
Returns:
Decorator function
"""
def error_404_view(self):
"""
Decorator for registering 404 error views.
Returns:
Decorator function
"""
def error_500_view(self):
"""
Decorator for registering 500 error views.
Returns:
Decorator function
"""
def add_template(self, name: str, string: str = '', path: str = ''):
"""
Add a template to the application.
Args:
name (str): Template name
string (str): Template content as string
path (str): Path to template file
"""
def add_static_file(self, name: str, string: str = '', path: str = ''):
"""
Add a static file to the application.
Args:
name (str): Static file name
string (str): File content as string
path (str): Path to static file
"""
def add_route(self, route: 'Route'):
"""
Programmatically add a route to the application.
Args:
route (Route): Route object to add
"""
def run(self, host: str = 'localhost', port: int = 8080,
loop_class=None, **kwargs):
"""
Run the application development server.
Args:
host (str): Server host address
port (int): Server port number
loop_class: Optional event loop class
**kwargs: Additional server arguments
"""
def setup_server(self, host: str = 'localhost', port: int = 8080):
"""
Setup the server without running it.
Args:
host (str): Server host address
port (int): Server port number
Returns:
Server instance
"""from lona import App, View
from lona.html import HTML, H1
app = App(__file__)
@app.route('/')
class IndexView(View):
def handle_request(self, request):
return HTML(H1('Hello World'))
# Add middleware
@app.middleware()
def my_middleware(request):
request.custom_data = 'processed'
return request
# Add template
app.add_template('base', '''
<html>
<head><title>{{ title }}</title></head>
<body>{{ content }}</body>
</html>
''')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)Base class for creating views that handle HTTP requests and user interactions. Views can be stateless (for simple HTTP responses) or stateful (for interactive applications with real-time user interaction).
class View:
def handle_request(self, request: 'Request'):
"""
Main request handler method - override this in subclasses.
Args:
request (Request): The HTTP request object
Returns:
Response object, HTML content, or None for interactive views
"""
def show(self, html=None, template: str = None, title: str = None):
"""
Display HTML content to the user and enter interactive mode.
Args:
html: HTML content to display
template (str): Template name to render
title (str): Page title
"""
def set_title(self, title: str):
"""
Set the page title.
Args:
title (str): New page title
"""
def await_input_event(self, *nodes, html=None, timeout=None) -> 'InputEvent':
"""
Wait for any input event on specified nodes.
Args:
*nodes: HTML nodes to monitor for events
html: Optional HTML to show before waiting
timeout: Optional timeout in seconds
Returns:
InputEvent object containing event details
"""
def await_click(self, *nodes, html=None, timeout=None) -> 'InputEvent':
"""
Wait for click events on specified nodes.
Args:
*nodes: HTML nodes to monitor for clicks
html: Optional HTML to show before waiting
timeout: Optional timeout in seconds
Returns:
InputEvent object containing click details
"""
def await_change(self, *nodes, html=None, timeout=None) -> 'InputEvent':
"""
Wait for change events on specified nodes.
Args:
*nodes: HTML nodes to monitor for changes
html: Optional HTML to show before waiting
timeout: Optional timeout in seconds
Returns:
InputEvent object containing change details
"""
def await_focus(self, *nodes, html=None, timeout=None) -> 'InputEvent':
"""
Wait for focus events on specified nodes.
Args:
*nodes: HTML nodes to monitor for focus
html: Optional HTML to show before waiting
timeout: Optional timeout in seconds
Returns:
InputEvent object containing focus details
"""
def await_blur(self, *nodes, html=None, timeout=None) -> 'InputEvent':
"""
Wait for blur events on specified nodes.
Args:
*nodes: HTML nodes to monitor for blur
html: Optional HTML to show before waiting
timeout: Optional timeout in seconds
Returns:
InputEvent object containing blur details
"""
def send_str(self, string: str, broadcast: bool = False):
"""
Send a string message to the frontend.
Args:
string (str): Message to send
broadcast (bool): Whether to broadcast to all connected clients
"""
def fire_view_event(self, name: str, data=None):
"""
Fire a custom view event.
Args:
name (str): Event name
data: Event data payload
"""
def subscribe(self, topic: str, handler):
"""
Subscribe to a channel topic.
Args:
topic (str): Channel topic name
handler: Function to handle messages
"""
def unsubscribe(self, topic: str = None):
"""
Unsubscribe from channel topics.
Args:
topic (str): Specific topic to unsubscribe from, or None for all
"""
def sleep(self, delay: float):
"""
Async sleep for the specified delay.
Args:
delay (float): Sleep duration in seconds
"""
def is_daemon_view(self) -> bool:
"""
Check if this view runs as a daemon.
Returns:
bool: True if view is a daemon
"""
def is_interactive(self) -> bool:
"""
Check if this view supports interactivity.
Returns:
bool: True if view is interactive
"""
# Properties
request: 'Request' # Current request object
server: object # Server instance
logger: object # Logger instancefrom lona import App, View
from lona.html import HTML, H1, Button, P
from lona.responses import JsonResponse
app = App(__file__)
# Simple HTTP view
@app.route('/api/status')
class StatusView(View):
def handle_request(self, request):
return JsonResponse({'status': 'ok'})
# Interactive view
@app.route('/counter')
class CounterView(View):
def handle_request(self, request):
count = 0
button = Button('Increment')
counter_text = P(f'Count: {count}')
html = HTML(
H1('Interactive Counter'),
counter_text,
button
)
self.show(html)
while True:
self.await_click(button)
count += 1
counter_text.set_text(f'Count: {count}')Represents an HTTP request with additional Lona-specific functionality for handling interactive applications, user sessions, and routing information.
class Request:
# Request data
GET: dict # GET parameters
POST: dict # POST parameters
method: str # HTTP method (GET, POST, etc.)
url: str # Request URL
# User and session
user: object # User object (if authentication middleware used)
# Routing information
route: 'Route' # Matched route object
match_info: dict # URL pattern match information
# Lona-specific
interactive: bool # Whether request supports interactivity
connection: object # Connection object for interactive features
view: 'View' # Associated view instance (set during processing)
# Headers and metadata
headers: dict # Request headers
content_type: str # Content type header
content_length: int # Content length
# Session data (if session middleware enabled)
session: dict # Session data dictionary@app.route('/user/<int:user_id>')
class UserProfileView(View):
def handle_request(self, request):
# Access URL parameters
user_id = request.match_info['user_id']
# Access GET/POST data
search_query = request.GET.get('q', '')
# Check request method
if request.method == 'POST':
# Handle form submission
name = request.POST.get('name', '')
# Access user (if authenticated)
if request.user:
username = request.user.username
# Check if interactive
if request.interactive:
# Can use interactive features
self.show(HTML(H1(f'User {user_id}')))
else:
# Return static response
return HtmlResponse(f'<h1>User {user_id}</h1>')Defines URL routing patterns and associates them with view classes or functions, supporting both interactive and non-interactive routes with optional HTTP pass-through.
class Route:
def __init__(self, raw_pattern: str, view, name: str = '',
interactive: bool = True, http_pass_through: bool = False,
frontend_view=None):
"""
Create a new route definition.
Args:
raw_pattern (str): URL pattern (supports placeholders like <int:id>)
view: View class or function to handle requests
name (str): Optional route name for reverse URL generation
interactive (bool): Whether route supports interactive features
http_pass_through (bool): Pass HTTP requests directly to view
frontend_view: Optional frontend view function
"""
# Properties
raw_pattern: str # Original URL pattern string
view: object # View class or function
name: str # Route name for reverse lookups
interactive: bool # Interactive support flag
http_pass_through: bool # HTTP pass-through flag
frontend_view: object # Frontend view functionfrom lona import App, Route, View
from lona.html import HTML, H1
app = App(__file__)
# Using decorator (recommended)
@app.route('/users/<int:user_id>', name='user_profile', interactive=True)
class UserView(View):
def handle_request(self, request):
user_id = request.match_info['user_id']
return HTML(H1(f'User {user_id}'))
# Programmatic route creation
class ApiView(View):
def handle_request(self, request):
return JsonResponse({'api': 'v1'})
api_route = Route('/api/v1', ApiView, name='api', interactive=False)
app.add_route(api_route)
# Route matching constant
from lona import MATCH_ALL
@app.route(MATCH_ALL, name='catch_all') # Matches any URL
class CatchAllView(View):
def handle_request(self, request):
return HTML(H1('Page not found'))from typing import Union, Optional, Dict, List, Callable, Any, Type
# Core framework types
ViewClass = Type['View']
ViewFunction = Callable[['Request'], Any]
ViewHandler = Union[ViewClass, ViewFunction]
MiddlewareHandler = Callable[['Request'], 'Request']
RoutePattern = Union[str, int] # int for MATCH_ALL constant
Settings = Union[object, Dict[str, Any]]Install with Tessl CLI
npx tessl i tessl/pypi-lona