CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-honcho

A Python clone of Foreman for managing Procfile-based applications with process management and export capabilities

Pending
Overview
Eval results
Files

export-system.mddocs/

Export System

Pluggable architecture for exporting Procfile-based applications to various process management systems including systemd, supervisord, upstart, and runit. The export system transforms Procfile configurations into native system service definitions.

Capabilities

Base Export Framework

The BaseExport class provides the foundation for all export plugins with template management and rendering capabilities.

class BaseExport:
    """
    Base class for all export plugins. Provides template management
    and rendering infrastructure using Jinja2 templates.
    """
    
    def __init__(self, template_dir=None, template_env=None):
        """
        Initialize export plugin with template configuration.
        
        Parameters:
        - template_dir: str, optional custom template directory path
        - template_env: jinja2.Environment, optional pre-configured template environment
        """
    
    def get_template(self, path):
        """
        Retrieve template at the specified path.
        
        Parameters:
        - path: str, template path relative to template directory
        
        Returns:
        jinja2.Template: loaded template object
        """
    
    def get_template_loader(self):
        """
        Get the template loader for this export plugin.
        Must be implemented by subclasses.
        
        Returns:
        jinja2.BaseLoader: template loader instance
        
        Raises:
        NotImplementedError: if not implemented by subclass
        """
    
    def render(self, processes, context):
        """
        Render processes to export format files.
        Must be implemented by subclasses.
        
        Parameters:
        - processes: list, expanded process definitions
        - context: dict, export context with app info and configuration
        
        Returns:
        Generator[File]: generator yielding File objects
        
        Raises:
        NotImplementedError: if not implemented by subclass
        """

File Representation

Data structure representing exported files with metadata.

class File:
    """
    Represents an exported file with name, content, and execution permissions.
    """
    
    def __init__(self, name, content, executable=False):
        """
        Initialize file representation.
        
        Parameters:
        - name: str, file name or path
        - content: str, file content
        - executable: bool, whether file should be executable (default: False)
        """
    
    # Properties
    name: str
    content: str
    executable: bool

Systemd Exporter

Export plugin for systemd service files and targets.

class SystemdExport(BaseExport):
    """
    Exporter for systemd service files and targets.
    Generates .service files for individual processes and .target files for grouping.
    """
    
    def get_template_loader(self):
        """Get systemd template loader."""
    
    def render(self, processes, context):
        """
        Render systemd service files and targets.
        
        Parameters:
        - processes: list, expanded process definitions
        - context: dict, export context with app, user, log directory, etc.
        
        Returns:
        Generator[File]: yields .service and .target files
        """

Supervisord Exporter

Export plugin for supervisord configuration files.

class SupervisordExport(BaseExport):
    """
    Exporter for supervisord configuration files.
    Generates a single .conf file with all process definitions.
    """
    
    def get_template_loader(self):
        """Get supervisord template loader."""
    
    def render(self, processes, context):
        """
        Render supervisord configuration file.
        
        Parameters:
        - processes: list, expanded process definitions
        - context: dict, export context
        
        Returns:
        Generator[File]: yields single .conf file
        """

Upstart Exporter

Export plugin for Ubuntu Upstart job definitions.

class UpstartExport(BaseExport):
    """
    Exporter for Ubuntu Upstart job definitions.
    Generates .conf files for individual processes and master job.
    """
    
    def get_template_loader(self):
        """Get upstart template loader."""
    
    def render(self, processes, context):
        """
        Render upstart job configuration files.
        
        Parameters:
        - processes: list, expanded process definitions
        - context: dict, export context
        
        Returns:
        Generator[File]: yields .conf files for jobs
        """

Runit Exporter

Export plugin for runit service directories.

class RunitExport(BaseExport):
    """
    Exporter for runit service directories.
    Generates run scripts and service directory structure.
    """
    
    def get_template_loader(self):
        """Get runit template loader."""
    
    def render(self, processes, context):
        """
        Render runit service directories and scripts.
        
        Parameters:
        - processes: list, expanded process definitions
        - context: dict, export context
        
        Returns:
        Generator[File]: yields run scripts and configuration files
        """

Template Utilities

Utility functions for template processing and string formatting.

def dashrepl(value):
    """
    Replace any non-word characters with dashes.
    Used for generating safe file and service names.
    
    Parameters:
    - value: str, input string
    
    Returns:
    str: string with non-word characters replaced by dashes
    """

def percentescape(value):
    """
    Double any percent signs for systemd compatibility.
    
    Parameters:
    - value: str, input string
    
    Returns:
    str: string with percent signs escaped
    """

Usage Examples

Basic Export Usage

from honcho.export.systemd import Export as SystemdExport
from honcho.environ import expand_processes, parse_procfile

# Parse Procfile and expand processes
procfile_content = """
web: python app.py
worker: python worker.py
"""
procfile = parse_procfile(procfile_content)

# Expand with concurrency and environment
processes = expand_processes(
    procfile.processes,
    concurrency={'web': 2, 'worker': 1},
    env={'DATABASE_URL': 'postgresql://localhost/myapp'},
    port=5000
)

# Create export context
context = {
    'app': 'myapp',
    'app_root': '/srv/myapp',
    'log': '/var/log/myapp',
    'shell': '/bin/bash',
    'user': 'myapp'
}

# Create exporter and render files
exporter = SystemdExport()
files = list(exporter.render(processes, context))

# Write files to filesystem
import os
for file in files:
    file_path = os.path.join('/etc/systemd/system', file.name)
    with open(file_path, 'w') as f:
        f.write(file.content)
    
    if file.executable:
        os.chmod(file_path, 0o755)
    
    print(f"Wrote {file_path}")

Custom Template Directory

from honcho.export.systemd import Export as SystemdExport

# Use custom template directory
exporter = SystemdExport(template_dir='/path/to/custom/templates')

# Or provide custom Jinja2 environment
import jinja2
template_env = jinja2.Environment(
    loader=jinja2.FileSystemLoader('/custom/templates'),
    trim_blocks=True,
    lstrip_blocks=True
)
exporter = SystemdExport(template_env=template_env)

Multiple Export Formats

from honcho.export.systemd import Export as SystemdExport
from honcho.export.supervisord import Export as SupervisordExport
from honcho.export.upstart import Export as UpstartExport

# Export to multiple formats
exporters = {
    'systemd': SystemdExport(),
    'supervisord': SupervisordExport(),
    'upstart': UpstartExport()
}

for format_name, exporter in exporters.items():
    output_dir = f'/tmp/export-{format_name}'
    os.makedirs(output_dir, exist_ok=True)
    
    files = list(exporter.render(processes, context))
    
    for file in files:
        file_path = os.path.join(output_dir, file.name)
        with open(file_path, 'w') as f:
            f.write(file.content)
        
        if file.executable:
            os.chmod(file_path, 0o755)
    
    print(f"Exported {len(files)} files to {output_dir}")

Plugin Discovery

The export system uses entry points for plugin discovery:

import sys
if sys.version_info < (3, 10):
    from backports.entry_points_selectable import entry_points
else:
    from importlib.metadata import entry_points

# Discover available export plugins
export_choices = dict(
    (_export.name, _export) for _export in entry_points(group="honcho_exporters")
)

print("Available exporters:")
for name in export_choices:
    print(f"  {name}")

# Load and use specific exporter
systemd_export_class = export_choices['systemd'].load()
exporter = systemd_export_class()

Context Configuration

# Typical export context structure
context = {
    'app': 'myapp',                    # Application name
    'app_root': '/srv/myapp',          # Application root directory
    'log': '/var/log/myapp',           # Log directory
    'shell': '/bin/bash',              # Shell to use for commands
    'user': 'myapp',                   # User to run processes as
    'template_dir': None,              # Custom template directory
}

# Context is passed to templates and can include custom variables
context.update({
    'environment': 'production',
    'restart_policy': 'always',
    'memory_limit': '512M'
})

Custom Export Plugin

import jinja2
from honcho.export.base import BaseExport, File

class CustomExport(BaseExport):
    """Custom export plugin example."""
    
    def get_template_loader(self):
        # Use package templates or custom directory
        return jinja2.PackageLoader('mypackage.templates', 'custom')
    
    def render(self, processes, context):
        template = self.get_template('service.conf')
        
        # Generate one file per process
        for process in processes:
            process_context = context.copy()
            process_context['process'] = process
            
            content = template.render(process_context)
            filename = f"{process.name}.conf"
            
            yield File(filename, content, executable=False)

# Register plugin via entry points in setup.py or pyproject.toml
# [project.entry-points.honcho_exporters]
# custom = "mypackage.export:CustomExport"

Template System

The export system uses Jinja2 templates with custom filters:

# Built-in template filters provided by BaseExport
env.filters['dashrepl'] = dashrepl           # Replace non-word chars with dashes
env.filters['percentescape'] = percentescape # Escape percent signs

# Additional filters can be added by exporters:
# env.filters['shellquote'] = shlex.quote    # Shell-safe quoting (if needed)

Example template usage:

[Unit]
Description={{ app }} ({{ process.name }})
After=network.target

[Service]
Type=simple
User={{ user }}
WorkingDirectory={{ app_root }}
Environment={{ process.env }}
ExecStart={{ process.cmd }}
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Install with Tessl CLI

npx tessl i tessl/pypi-honcho

docs

command-line-interface.md

environment-configuration.md

export-system.md

index.md

output-formatting.md

process-management.md

tile.json