CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-bottle

Fast and simple WSGI micro web-framework for small web applications with no dependencies other than the Python Standard Library.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

Plugin architecture for extending Bottle functionality with custom middleware, request/response processing, hooks, and built-in plugins for JSON handling and template rendering.

Capabilities

Plugin Management

Install, uninstall, and manage plugins for applications.

def install(plugin):
    """
    Install a plugin on the default application.
    
    Parameters:
    - plugin: plugin instance, class, or callable
    """

def uninstall(plugin):
    """
    Uninstall plugins from the default application.
    
    Parameters:
    - plugin: plugin instance, class, type, or string name
    """

class Bottle:
    def install(self, plugin):
        """
        Install a plugin on this application.
        
        Parameters:
        - plugin: plugin instance, class, or callable
        
        Returns:
        plugin: installed plugin instance
        """
    
    def uninstall(self, plugin):
        """
        Uninstall plugins from this application.
        
        Parameters:
        - plugin: plugin instance, class, type, or string name
        
        Returns:
        list: list of uninstalled plugins
        """

Usage:

from bottle import install, uninstall, Bottle

# Install plugin on default app
install(MyPlugin())

# Install by class (will instantiate)
install(MyPlugin)

# Uninstall by instance
plugin = MyPlugin()
install(plugin)
uninstall(plugin)

# Uninstall by type
uninstall(MyPlugin)

# Uninstall by name
uninstall('myplugin')

# Application-specific plugins
app = Bottle()
app.install(MyPlugin())
app.uninstall(MyPlugin)

Hook System

Register callbacks for application lifecycle events.

def hook(name):
    """
    Hook decorator for the default application.
    
    Parameters:
    - name: str, hook name ('before_request', 'after_request', 'app_reset', 'config')
    
    Returns:
    function: decorator function
    """

class Bottle:
    def hook(self, name):
        """Hook decorator for this application."""
    
    def add_hook(self, name, func):
        """
        Add hook callback directly.
        
        Parameters:
        - name: str, hook name
        - func: callable, hook function
        """
    
    def remove_hook(self, name, func):
        """
        Remove hook callback.
        
        Parameters:
        - name: str, hook name  
        - func: callable, hook function to remove
        
        Returns:
        bool: True if hook was removed
        """
    
    def trigger_hook(self, name, *args, **kwargs):
        """
        Trigger hook callbacks.
        
        Parameters:
        - name: str, hook name
        - *args: hook arguments
        - **kwargs: hook keyword arguments
        
        Returns:
        list: list of hook return values
        """

Available hooks:

  • before_request: Called before each request is processed
  • after_request: Called after each request is processed
  • app_reset: Called when application is reset
  • config: Called when configuration changes

Usage:

@hook('before_request')
def before_request():
    print(f"Processing request: {request.method} {request.path}")

@hook('after_request')
def after_request():
    print(f"Request completed: {response.status}")

@hook('config')
def config_changed(key, old_value, new_value):
    print(f"Config {key} changed from {old_value} to {new_value}")

# Direct hook management
app = Bottle()

def log_request():
    print(f"Request: {request.path}")

app.add_hook('before_request', log_request)
app.remove_hook('before_request', log_request)

Creating Plugins

Create custom plugins by implementing the plugin interface.

class Plugin:
    """Base plugin interface."""
    
    name = 'plugin'  # Plugin name for identification
    api = 2          # Plugin API version
    
    def apply(self, callback, route):
        """
        Apply plugin to route callback.
        
        Parameters:
        - callback: function, route callback function
        - route: Route, route instance
        
        Returns:
        function: wrapped callback function
        """
    
    def setup(self, app):
        """
        Setup plugin when installed.
        
        Parameters:
        - app: Bottle, application instance
        """
    
    def close(self):
        """Cleanup when plugin is uninstalled."""

Simple plugin example:

class TimingPlugin:
    name = 'timing'
    api = 2
    
    def apply(self, callback, route):
        import time
        
        def wrapper(*args, **kwargs):
            start = time.time()
            try:
                return callback(*args, **kwargs)
            finally:
                duration = time.time() - start
                print(f"Route {route.rule} took {duration:.3f}s")
        
        return wrapper

# Install timing plugin
install(TimingPlugin())

Built-in Plugins

Bottle includes several built-in plugins for common functionality.

JSON Plugin

Automatic JSON serialization for dictionary return values.

class JSONPlugin:
    name = 'json'
    api = 2
    
    def __init__(self, json_dumps=json.dumps):
        """
        JSON serialization plugin.
        
        Parameters:
        - json_dumps: function, JSON serialization function
        """
    
    def apply(self, callback, route):
        """Apply JSON serialization to route."""

Configuration:

import json
from bottle import JSONPlugin, install

# Custom JSON encoder
def custom_dumps(obj):
    return json.dumps(obj, indent=2, sort_keys=True)

# Install with custom encoder
install(JSONPlugin(json_dumps=custom_dumps))

# Configure via app config
app.config['json.enable'] = True
app.config['json.ascii_only'] = False
app.config['json.dumps'] = custom_dumps

Usage:

@route('/api/data')
def api_data():
    # Automatically serialized to JSON
    return {
        'status': 'success',
        'data': ['item1', 'item2', 'item3'],
        'count': 3
    }

Template Plugin

Automatic template rendering for dictionary return values.

class TemplatePlugin:
    name = 'template'
    api = 2
    
    def apply(self, callback, route):
        """Apply template rendering to route."""

Usage with template decorator:

@route('/user/<id:int>')
@view('user.html')
def show_user(id):
    # Return dict is passed to template
    return {
        'user': get_user(id),
        'title': f'User {id}'
    }

Plugin Configuration

Configure plugin behavior and route-specific settings.

Route-specific Plugin Control

@route('/api/raw', apply=['json'])  # Only apply json plugin
def raw_api():
    return {'raw': True}

@route('/api/skip', skip=['json'])  # Skip json plugin
def skip_json():
    return "Raw string response"

@route('/template', template='page.html')  # Template plugin config
def templated():
    return {'content': 'Hello World'}

Plugin Priorities

Control plugin execution order:

class HighPriorityPlugin:
    name = 'high'
    api = 2
    
    def apply(self, callback, route):
        def wrapper(*args, **kwargs):
            print("Before high priority")
            result = callback(*args, **kwargs)
            print("After high priority")
            return result
        return wrapper

class LowPriorityPlugin:
    name = 'low'
    api = 2
    
    def apply(self, callback, route):
        def wrapper(*args, **kwargs):
            print("Before low priority")
            result = callback(*args, **kwargs)
            print("After low priority")
            return result
        return wrapper

# Install in order (first installed wraps innermost)
install(LowPriorityPlugin())
install(HighPriorityPlugin())

Advanced Plugin Patterns

Context Plugins

Plugins that provide request context:

class DatabasePlugin:
    name = 'db'
    api = 2
    
    def __init__(self, database_url):
        self.database_url = database_url
        self.pool = None
    
    def setup(self, app):
        self.pool = create_connection_pool(self.database_url)
    
    def apply(self, callback, route):
        def wrapper(*args, **kwargs):
            with self.pool.get_connection() as db:
                # Add db to request context
                bottle.request.db = db
                try:
                    return callback(*args, **kwargs)
                finally:
                    # Clean up
                    del bottle.request.db
        return wrapper
    
    def close(self):
        if self.pool:
            self.pool.close()

# Usage in routes
@route('/users')
def list_users():
    users = request.db.query('SELECT * FROM users')
    return {'users': users}

Authentication Plugins

class AuthPlugin:
    name = 'auth'
    api = 2
    
    def __init__(self, auth_func, skip_paths=None):
        self.auth_func = auth_func
        self.skip_paths = skip_paths or []
    
    def apply(self, callback, route):
        # Skip authentication for certain paths
        if route.rule in self.skip_paths:
            return callback
        
        def wrapper(*args, **kwargs):
            if not self.auth_func(request):
                abort(401, 'Authentication required')
            return callback(*args, **kwargs)
        return wrapper

def check_auth(request):
    token = request.get_header('Authorization')
    return token and validate_token(token)

# Install auth plugin
install(AuthPlugin(check_auth, skip_paths=['/login', '/public']))

Caching Plugins

class CachePlugin:
    name = 'cache'
    api = 2
    
    def __init__(self, cache_store):
        self.cache = cache_store
    
    def apply(self, callback, route):
        # Only cache GET requests
        if getattr(route, 'method', 'GET') != 'GET':
            return callback
        
        def wrapper(*args, **kwargs):
            cache_key = f"{route.rule}:{request.query_string}"
            
            # Try cache first
            cached = self.cache.get(cache_key)
            if cached:
                return cached
            
            # Generate response
            result = callback(*args, **kwargs)
            
            # Cache result
            self.cache.set(cache_key, result, timeout=300)
            return result
        
        return wrapper

Plugin Error Handling

Handle errors in plugin processing:

class PluginError(BottleException):
    def __init__(self, message):
        """Plugin-specific error exception."""

Error handling in plugins:

class SafePlugin:
    name = 'safe'
    api = 2
    
    def apply(self, callback, route):
        def wrapper(*args, **kwargs):
            try:
                return callback(*args, **kwargs)
            except Exception as e:
                print(f"Plugin error in {route.rule}: {e}")
                # Could log, send to monitoring, etc.
                raise  # Re-raise or return error response
        return wrapper

Install with Tessl CLI

npx tessl i tessl/pypi-bottle

docs

application-routing.md

index.md

plugin-system.md

request-handling.md

response-management.md

server-management.md

static-utilities.md

template-rendering.md

tile.json