Write responsive web apps in full python
—
Various response classes for different content types and HTTP behaviors, including HTML responses, JSON responses, template responses, file downloads, and redirects. The response system provides a flexible way to return different types of content from views.
Foundation response classes that all other responses inherit from, providing basic HTTP response functionality.
class AbstractResponse:
"""Abstract base class for all response types."""
def __init__(self):
"""Initialize abstract response."""
# Properties
interactive: bool # Whether response can be used in interactive views
class Response(AbstractResponse):
def __init__(self, content: str = '', status: int = 200, headers: dict = None,
content_type: str = 'text/plain'):
"""
Create a basic HTTP response.
Args:
content (str): Response content
status (int): HTTP status code
headers (dict): HTTP headers
content_type (str): Content-Type header
"""
# Properties
content: str # Response content
status: int # HTTP status code
headers: dict # HTTP headers
content_type: str # Content-Type header
interactive: bool # Whether response can be used in interactive viewsResponses for returning HTML content to the browser.
class HtmlResponse(Response):
def __init__(self, html, status: int = 200, headers: dict = None):
"""
Create an HTML response.
Args:
html: HTML content (Node objects or string)
status (int): HTTP status code
headers (dict): HTTP headers
"""
class TemplateResponse(Response):
def __init__(self, template_name: str, context: dict = None,
status: int = 200, headers: dict = None):
"""
Create a template-based response.
Args:
template_name (str): Name of template to render
context (dict): Template context variables
status (int): HTTP status code
headers (dict): HTTP headers
"""
class TemplateStringResponse(Response):
def __init__(self, template_string: str, context: dict = None,
status: int = 200, headers: dict = None):
"""
Create a response from template string.
Args:
template_string (str): Template content as string
context (dict): Template context variables
status (int): HTTP status code
headers (dict): HTTP headers
"""from lona import App, View
from lona.html import HTML, H1, P
from lona.responses import HtmlResponse, TemplateResponse
app = App(__file__)
@app.route('/simple')
class SimpleView(View):
def handle_request(self, request):
html = HTML(
H1('Simple Response'),
P('This is a simple HTML response.')
)
return HtmlResponse(html)
@app.route('/template')
class TemplateView(View):
def handle_request(self, request):
context = {
'title': 'Template Example',
'username': request.user.username if request.user else 'Guest'
}
return TemplateResponse('base.html', context)Responses for returning structured data in various formats.
class JsonResponse(Response):
def __init__(self, json_data, status: int = 200, headers: dict = None):
"""
Create a JSON response.
Args:
json_data: Data to serialize as JSON
status (int): HTTP status code
headers (dict): HTTP headers
"""from lona.responses import JsonResponse
@app.route('/api/users')
class UserApiView(View):
def handle_request(self, request):
users = [
{'id': 1, 'name': 'John', 'email': 'john@example.com'},
{'id': 2, 'name': 'Jane', 'email': 'jane@example.com'}
]
return JsonResponse({
'users': users,
'count': len(users),
'status': 'success'
})
@app.route('/api/user/<int:user_id>')
class SingleUserApiView(View):
def handle_request(self, request):
user_id = request.match_info['user_id']
if user_id == 404:
return JsonResponse(
{'error': 'User not found'},
status=404
)
return JsonResponse({
'id': user_id,
'name': f'User {user_id}',
'active': True
})Responses for serving files and downloads.
class FileResponse(Response):
def __init__(self, path: str, content_type: str = None,
as_attachment: bool = False, headers: dict = None):
"""
Create a file download response.
Args:
path (str): Path to file on filesystem
content_type (str): MIME type (auto-detected if None)
as_attachment (bool): Force download vs inline display
headers (dict): Additional HTTP headers
"""
# Properties
path: str # File system path
as_attachment: bool # Download behavior flagimport os
from lona.responses import FileResponse
@app.route('/download/<filename>')
class DownloadView(View):
def handle_request(self, request):
filename = request.match_info['filename']
file_path = os.path.join('uploads', filename)
if not os.path.exists(file_path):
return JsonResponse({'error': 'File not found'}, status=404)
return FileResponse(
path=file_path,
as_attachment=True, # Force download
headers={'X-Download-Source': 'lona-app'}
)
@app.route('/image/<filename>')
class ImageView(View):
def handle_request(self, request):
filename = request.match_info['filename']
image_path = os.path.join('static', 'images', filename)
return FileResponse(
path=image_path,
content_type='image/jpeg',
as_attachment=False # Display inline
)Responses for redirecting users to different URLs.
class RedirectResponse(Response):
def __init__(self, redirect_url: str):
"""
Create an internal redirect response.
Args:
redirect_url (str): URL to redirect to within the app
"""
class HttpRedirectResponse(Response):
def __init__(self, redirect_url: str, status: int = 302):
"""
Create an HTTP redirect response.
Args:
redirect_url (str): URL to redirect to (can be external)
status (int): HTTP redirect status code (301, 302, 303, 307, 308)
"""
# Properties
redirect_url: str # Target URLfrom lona.responses import RedirectResponse, HttpRedirectResponse
@app.route('/login')
class LoginView(View):
def handle_request(self, request):
if request.user:
# User already logged in, redirect to dashboard
return RedirectResponse('/dashboard')
# Show login form
return self.show_login_form()
@app.route('/external-redirect')
class ExternalRedirectView(View):
def handle_request(self, request):
# Redirect to external site
return HttpRedirectResponse(
'https://external-site.com/page',
status=301 # Permanent redirect
)
@app.route('/logout')
class LogoutView(View):
def handle_request(self, request):
# Perform logout logic
request.session.clear()
# Redirect to home page
return RedirectResponse('/')Specialized responses for handling error conditions.
class ErrorResponse(Response):
def __init__(self, message: str, status: int = 500, headers: dict = None):
"""
Create an error response.
Args:
message (str): Error message
status (int): HTTP error status code
headers (dict): HTTP headers
"""
class NotFoundResponse(ErrorResponse):
def __init__(self, message: str = 'Not Found', headers: dict = None):
"""
Create a 404 Not Found response.
Args:
message (str): Error message
headers (dict): HTTP headers
"""
class ForbiddenResponse(ErrorResponse):
def __init__(self, message: str = 'Forbidden', headers: dict = None):
"""
Create a 403 Forbidden response.
Args:
message (str): Error message
headers (dict): HTTP headers
"""from lona.responses import NotFoundResponse, ForbiddenResponse, ErrorResponse
@app.route('/admin/<path:admin_path>')
class AdminView(View):
def handle_request(self, request):
if not request.user or not request.user.is_admin:
return ForbiddenResponse('Admin access required')
admin_path = request.match_info['admin_path']
if admin_path not in ['users', 'settings', 'logs']:
return NotFoundResponse(f'Admin page "{admin_path}" not found')
try:
return self.render_admin_page(admin_path)
except Exception as e:
return ErrorResponse(
f'Internal server error: {str(e)}',
status=500
)Utilities for managing HTTP headers and cookies in responses.
# Response header utilities (available on all response classes)
def set_header(self, name: str, value: str):
"""Set an HTTP header."""
def get_header(self, name: str) -> str:
"""Get an HTTP header value."""
def remove_header(self, name: str):
"""Remove an HTTP header."""
def set_cookie(self, name: str, value: str, max_age: int = None,
expires: str = None, path: str = '/', domain: str = None,
secure: bool = False, httponly: bool = False):
"""Set an HTTP cookie."""
def delete_cookie(self, name: str, path: str = '/', domain: str = None):
"""Delete an HTTP cookie."""from lona.responses import JsonResponse, RedirectResponse
@app.route('/api/login')
class LoginApiView(View):
def handle_request(self, request):
# Validate credentials...
if valid_credentials:
response = JsonResponse({'status': 'logged_in'})
# Set authentication cookie
response.set_cookie(
'auth_token',
'abc123def456',
max_age=3600, # 1 hour
httponly=True,
secure=True
)
# Set custom headers
response.set_header('X-Login-Time', '2023-01-01 12:00:00')
response.set_header('Cache-Control', 'no-cache')
return response
else:
return JsonResponse({'error': 'Invalid credentials'}, status=401)
@app.route('/logout')
class LogoutView(View):
def handle_request(self, request):
response = RedirectResponse('/')
# Clear authentication cookie
response.delete_cookie('auth_token')
return responsefrom typing import Union, Optional, Dict, Any
from lona.html import Node
# Content types
ResponseContent = Union[str, bytes, Node, dict, list]
HTMLContent = Union[str, Node, list]
JSONContent = Union[dict, list, str, int, float, bool, None]
# Response types
HTTPStatus = int
HTTPHeaders = Optional[Dict[str, str]]
TemplateContext = Optional[Dict[str, Any]]
# Cookie types
CookieName = str
CookieValue = str
CookieExpires = Optional[str]
CookieMaxAge = Optional[int]Install with Tessl CLI
npx tessl i tessl/pypi-lona