CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-jupyterhub

A multi-user server for Jupyter notebooks that provides authentication, spawning, and proxying for multiple users simultaneously

Pending
Overview
Eval results
Files

configuration-utilities.mddocs/

Configuration and Utilities

JupyterHub provides a comprehensive configuration system using traitlets along with utility functions for common operations including token management, URL handling, SSL setup, and asynchronous programming patterns.

Capabilities

Configuration System (Traitlets)

Custom traitlet types for JupyterHub configuration validation and processing.

class Command(TraitType):
    """
    Traitlet for command-line commands with shell and string list support.
    
    Validates and processes command specifications for spawners and services.
    """
    
    def validate(self, obj, value):
        """
        Validate command specification.
        
        Args:
            obj: Object being configured
            value: Command value (string, list, or callable)
            
        Returns:
            Validated command as list of strings
            
        Raises:
            TraitError: If command format is invalid
        """

class URLPrefix(TraitType):
    """
    Traitlet for URL prefix validation and normalization.
    
    Ensures URL prefixes are properly formatted with leading/trailing slashes.
    """
    
    def validate(self, obj, value):
        """
        Validate and normalize URL prefix.
        
        Args:
            obj: Object being configured
            value: URL prefix string
            
        Returns:
            Normalized URL prefix with proper slash handling
        """

class ByteSpecification(TraitType):
    """
    Traitlet for memory/storage size specifications with unit support.
    
    Accepts values like '1G', '512M', '2048K' and converts to bytes.
    """
    
    UNIT_MAP = {
        'K': 1024,
        'M': 1024**2, 
        'G': 1024**3,
        'T': 1024**4
    }
    
    def validate(self, obj, value):
        """
        Validate and convert byte specification to integer bytes.
        
        Args:
            obj: Object being configured
            value: Size specification (int, string with units)
            
        Returns:
            Size in bytes as integer
        """

class Callable(TraitType):
    """
    Traitlet for callable objects with import string support.
    
    Accepts callable objects or import strings that resolve to callables.
    """
    
    def validate(self, obj, value):
        """
        Validate and resolve callable specification.
        
        Args:
            obj: Object being configured  
            value: Callable or import string
            
        Returns:
            Resolved callable object
        """

class EntryPointType(TraitType):
    """
    Traitlet for loading Python entry points for plugin systems.
    
    Supports authenticators, spawners, proxies, and other plugin types.
    """
    
    def __init__(self, entry_point_group, **kwargs):
        """
        Initialize entry point loader.
        
        Args:
            entry_point_group: Entry point group name (e.g., 'jupyterhub.authenticators')
            **kwargs: Additional traitlet options
        """
        self.entry_point_group = entry_point_group
        super().__init__(**kwargs)
    
    def validate(self, obj, value):
        """
        Load plugin from entry point specification.
        
        Args:
            obj: Object being configured
            value: Entry point name or class
            
        Returns:
            Loaded plugin class
        """

Token Management Utilities

Secure token generation, hashing, and validation functions.

def new_token(length: int = 32, entropy: int = None) -> str:
    """
    Generate a new random API token.
    
    Args:
        length: Token length in characters (default: 32)
        entropy: Additional entropy source (optional)
        
    Returns:
        Random token string (URL-safe base64)
    """

def hash_token(token: str, salt: bytes = None, rounds: int = 16384) -> str:
    """
    Hash a token for secure storage using PBKDF2.
    
    Args:
        token: Token string to hash
        salt: Salt bytes (generated if None)
        rounds: PBKDF2 iteration count
        
    Returns:
        Base64-encoded hash suitable for database storage
    """

def compare_token(token: str, hashed: str) -> bool:
    """
    Compare a token against its stored hash.
    
    Args:
        token: Plain token string
        hashed: Stored hash from hash_token()
        
    Returns:
        True if token matches hash
    """

def token_authenticated(method):
    """
    Decorator for methods that require token authentication.
    
    Args:
        method: Method to protect with token auth
        
    Returns:
        Decorated method that checks for valid token
    """

URL and Path Utilities

Functions for safe URL and path manipulation in web contexts.

def url_path_join(*pieces) -> str:
    """
    Join URL path components with proper slash handling.
    
    Args:
        *pieces: URL path components to join
        
    Returns:
        Properly joined URL path with normalized slashes
    """

def url_escape_path(path: str, safe: str = '@!:$&\'()*+,;=') -> str:
    """
    URL-encode path components while preserving safe characters.
    
    Args:
        path: Path string to encode
        safe: Characters to leave unencoded
        
    Returns:
        URL-encoded path string
    """

def url_unescape_path(path: str) -> str:
    """
    URL-decode path components.
    
    Args:
        path: URL-encoded path string
        
    Returns:
        Decoded path string
    """

def guess_base_url(url: str) -> str:
    """
    Guess the base URL from a full URL.
    
    Args:
        url: Full URL string
        
    Returns:
        Base URL (protocol + host + port)
    """

Async and Concurrency Utilities

Helper functions for asynchronous programming patterns in JupyterHub.

def maybe_future(value):
    """
    Convert value to Future if it's not already awaitable.
    
    Args:
        value: Value that may or may not be awaitable
        
    Returns:
        Future/coroutine that can be awaited
    """

def exponential_backoff(func, 
                       max_retries: int = 5,
                       initial_delay: float = 1.0,
                       max_delay: float = 60.0,
                       backoff_factor: float = 2.0):
    """
    Decorator for exponential backoff retry logic.
    
    Args:
        func: Function to wrap with retry logic
        max_retries: Maximum number of retry attempts
        initial_delay: Initial delay between retries (seconds)
        max_delay: Maximum delay between retries (seconds)
        backoff_factor: Multiplier for delay on each retry
        
    Returns:
        Decorated function with retry logic
    """

async def cancel_tasks(tasks):
    """
    Cancel a collection of asyncio tasks gracefully.
    
    Args:
        tasks: Iterable of asyncio tasks to cancel
    """

def run_sync(async_func):
    """
    Run an async function synchronously.
    
    Args:
        async_func: Async function to run
        
    Returns:
        Result of the async function
    """

Network and SSL Utilities

Functions for network operations and SSL context management.

def make_ssl_context(keyfile: str = None, 
                    certfile: str = None,
                    cafile: str = None,
                    verify_mode: ssl.VerifyMode = None,
                    check_hostname: bool = None,
                    **kwargs) -> ssl.SSLContext:
    """
    Create SSL context with common JupyterHub defaults.
    
    Args:
        keyfile: Path to SSL private key file
        certfile: Path to SSL certificate file  
        cafile: Path to CA certificate file
        verify_mode: SSL verification mode
        check_hostname: Whether to verify hostname
        **kwargs: Additional SSL context options
        
    Returns:
        Configured SSL context
    """

def random_port() -> int:
    """
    Find a random available port.
    
    Returns:
        Available port number
    """

def is_valid_ip(ip: str) -> bool:
    """
    Check if string is a valid IP address.
    
    Args:
        ip: IP address string to validate
        
    Returns:
        True if valid IPv4 or IPv6 address
    """

def get_server_info(url: str) -> Dict[str, Any]:
    """
    Get server information from URL.
    
    Args:
        url: Server URL to analyze
        
    Returns:
        Dictionary with host, port, protocol info
    """

Database and ORM Utilities

Utilities for database operations and schema management.

def mysql_large_prefix_check():
    """
    Check MySQL configuration for large index prefix support.
    
    Raises:
        DatabaseError: If MySQL doesn't support required index sizes
    """

def upgrade_if_needed(db_url: str, log=None):
    """
    Upgrade database schema if needed.
    
    Args:
        db_url: Database connection URL
        log: Logger instance for output
    """

def get_schema_version(db_url: str) -> str:
    """
    Get current database schema version.
    
    Args:
        db_url: Database connection URL
        
    Returns:
        Schema version string
    """

Usage Examples

Configuration with Custom Traitlets

# jupyterhub_config.py using custom traitlets
c = get_config()

# Memory limits using ByteSpecification
c.Spawner.mem_limit = '2G'  # Converted to 2147483648 bytes
c.Spawner.mem_guarantee = '512M'  # Converted to 536870912 bytes

# URL prefix configuration
c.JupyterHub.base_url = '/hub/'  # Normalized to '/hub/'

# Command configuration
c.Spawner.cmd = ['jupyterhub-singleuser']  # Validated as command list
c.LocalProcessSpawner.shell_cmd = ['/bin/bash', '-l', '-c']

# Plugin loading via entry points
c.JupyterHub.authenticator_class = 'pam'  # Loads PAMAuthenticator
c.JupyterHub.spawner_class = 'localprocess'  # Loads LocalProcessSpawner

Token Management

from jupyterhub.utils import new_token, hash_token, compare_token
from jupyterhub.orm import APIToken

# Generate API token for user
user = db.query(User).filter(User.name == 'alice').first()
token = new_token(length=32)
hashed = hash_token(token)

# Store in database
api_token = APIToken(
    user=user,
    hashed=hashed,
    prefix=token[:4],  # Store prefix for identification
    note='CLI access token'
)
db.add(api_token)
db.commit()

# Validate token later
def validate_api_token(provided_token):
    """Validate API token against database"""
    prefix = provided_token[:4]
    token_record = db.query(APIToken).filter(
        APIToken.prefix == prefix
    ).first()
    
    if token_record and compare_token(provided_token, token_record.hashed):
        return token_record.user
    return None

URL Handling

from jupyterhub.utils import url_path_join, url_escape_path

# Safe URL path joining
base_url = '/hub'
user_name = 'alice@example.com'
server_name = 'my-server'

# Build user server URL safely
server_url = url_path_join(
    base_url,
    'user',
    url_escape_path(user_name),
    url_escape_path(server_name)
)
# Result: '/hub/user/alice%40example.com/my-server'

# Build API endpoint URLs
api_url = url_path_join(base_url, 'api', 'users', url_escape_path(user_name))
# Result: '/hub/api/users/alice%40example.com'

Async Utilities

from jupyterhub.utils import maybe_future, exponential_backoff
import asyncio

# Convert sync/async values consistently
async def handle_result(result):
    """Handle potentially async result"""
    # maybe_future ensures we can always await
    final_result = await maybe_future(result)
    return final_result

# Retry with exponential backoff
@exponential_backoff(max_retries=3, initial_delay=1.0)
async def unreliable_operation():
    """Operation that might fail temporarily"""
    # Simulate operation that might fail
    if random.random() < 0.5:
        raise Exception("Temporary failure")
    return "Success"

# Usage
try:
    result = await unreliable_operation()
    print(f"Operation succeeded: {result}")
except Exception as e:
    print(f"Operation failed after retries: {e}")

SSL Configuration

from jupyterhub.utils import make_ssl_context

# Create SSL context for HTTPS
ssl_context = make_ssl_context(
    keyfile='/path/to/private.key',
    certfile='/path/to/certificate.crt',
    cafile='/path/to/ca-bundle.crt'
)

# Use in JupyterHub configuration
c.JupyterHub.ssl_key = '/path/to/private.key'
c.JupyterHub.ssl_cert = '/path/to/certificate.crt'

# Or programmatically
app = JupyterHub()
app.ssl_context = ssl_context

Custom Configuration Classes

from traitlets import Unicode, Integer, Bool
from traitlets.config import Configurable
from jupyterhub.traitlets import ByteSpecification, Command

class CustomSpawner(Spawner):
    """Custom spawner with additional configuration"""
    
    # Memory configuration using ByteSpecification
    memory_limit = ByteSpecification(
        config=True,
        help="""
        Memory limit for user servers.
        Specify with units like '1G', '512M', etc.
        """
    )
    
    # Custom command configuration
    setup_command = Command(
        config=True,
        help="""
        Command to run before starting notebook server.
        Can be string or list of strings.
        """
    )
    
    # Container image with validation
    image = Unicode(
        'jupyter/base-notebook',
        config=True,
        help="Docker image to use for user servers"
    )
    
    # Advanced configuration
    privileged = Bool(
        False,
        config=True,
        help="Whether to run containers in privileged mode"
    )
    
    async def start(self):
        """Start server with custom configuration"""
        # Use configured values
        print(f"Memory limit: {self.memory_limit} bytes")
        print(f"Setup command: {self.setup_command}")
        print(f"Image: {self.image}")
        
        # Custom startup logic here
        return await super().start()

# Usage in config
c.JupyterHub.spawner_class = CustomSpawner
c.CustomSpawner.memory_limit = '4G'
c.CustomSpawner.setup_command = ['conda', 'activate', 'myenv']
c.CustomSpawner.image = 'myregistry/custom-notebook:latest'

Database Utilities

from jupyterhub.dbutil import upgrade_if_needed

# Automatic database upgrades
def initialize_database(db_url):
    """Initialize database with schema upgrades"""
    try:
        upgrade_if_needed(db_url, log=app.log)
        app.log.info("Database schema is up to date")
    except Exception as e:
        app.log.error(f"Database upgrade failed: {e}")
        raise

# Use in application startup
app = JupyterHub()
initialize_database(app.db_url)
app.start()

Advanced Configuration Patterns

Environment-Based Configuration

import os
from jupyterhub.utils import url_path_join

# Environment-aware configuration
def get_config():
    """Get configuration based on environment"""
    c = super().get_config()
    
    # Database URL from environment
    c.JupyterHub.db_url = os.environ.get(
        'JUPYTERHUB_DB_URL',
        'sqlite:///jupyterhub.sqlite'
    )
    
    # Base URL handling
    base_url = os.environ.get('JUPYTERHUB_BASE_URL', '/')
    c.JupyterHub.base_url = base_url
    
    # SSL in production
    if os.environ.get('JUPYTERHUB_ENV') == 'production':
        c.JupyterHub.ssl_key = os.environ['SSL_KEY_PATH']
        c.JupyterHub.ssl_cert = os.environ['SSL_CERT_PATH']
        c.JupyterHub.port = 443
    else:
        c.JupyterHub.port = 8000
    
    return c

Dynamic Configuration Updates

class DynamicConfig(Configurable):
    """Configuration that can be updated at runtime"""
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.config_watchers = []
    
    def watch_config_changes(self, callback):
        """Register callback for configuration changes"""
        self.config_watchers.append(callback)
    
    def update_config(self, **kwargs):
        """Update configuration and notify watchers"""
        for key, value in kwargs.items():
            if hasattr(self, key):
                setattr(self, key, value)
        
        # Notify watchers
        for callback in self.config_watchers:
            callback(kwargs)

# Usage
config = DynamicConfig()
config.watch_config_changes(lambda changes: print(f"Config updated: {changes}"))
config.update_config(memory_limit='8G', image='new-image:latest')

Install with Tessl CLI

npx tessl i tessl/pypi-jupyterhub

docs

authentication.md

configuration-utilities.md

core-application.md

database-models.md

index.md

monitoring-metrics.md

rbac-permissions.md

rest-api.md

services-oauth.md

singleuser-integration.md

spawners.md

tile.json