A Python clone of Foreman for managing Procfile-based applications with process management and export capabilities
—
System for parsing Procfiles, managing environment variables, and expanding process configurations with concurrency and port assignment. This module handles all aspects of configuration management for Procfile-based applications.
The Env class provides configuration management with support for Procfiles, environment variables, and port configuration.
class Env:
"""
Environment configuration manager that handles Procfile loading,
port configuration, and application root directory management.
"""
def __init__(self, config):
"""
Initialize environment with configuration dictionary.
Parameters:
- config: dict, configuration mapping (typically from ChainMap)
"""
def load_procfile(self):
"""
Load and parse the Procfile.
Returns:
Procfile object with parsed processes
Raises:
IOError: if Procfile doesn't exist or isn't readable
AssertionError: if Procfile format is invalid
"""
@property
def port(self):
"""
Get port as integer from configuration.
Returns:
int: port number
Raises:
ValueError: if port value is invalid
"""
@property
def procfile(self):
"""
Get full path to Procfile.
Returns:
str: absolute path to Procfile
"""Data structures and functions for parsing and managing Procfile content.
class Procfile:
"""
Data structure representing a Procfile with process definitions.
"""
def __init__(self):
"""Initialize empty Procfile."""
def add_process(self, name, command):
"""
Add a process to the Procfile.
Parameters:
- name: str, process name (must be unique)
- command: str, command to execute
Raises:
AssertionError: if process name is not unique
"""
# Properties
processes: 'OrderedDict[str, str]' # Mapping of process names to commandsdef parse_procfile(contents):
"""
Parse Procfile content into a Procfile object.
Parameters:
- contents: str, raw Procfile content
Returns:
Procfile object with parsed processes
"""Functions for parsing .env files and environment variable configuration.
def parse(content):
"""
Parse the content of a .env file (KEY=value format) into a dictionary.
Parameters:
- content: str, .env file content with KEY=value lines
Returns:
dict: mapping of environment variable names to values
"""Functions for expanding process definitions with concurrency, environment variables, and port assignment.
def expand_processes(processes, concurrency=None, env=None, quiet=None, port=None):
"""
Get a list of processes that need to be started given the specified
process types, concurrency, environment, quietness, and base port number.
Parameters:
- processes: dict, mapping of process names to commands
- concurrency: dict, optional concurrency specification per process type
- env: dict, optional environment variables to apply to all processes
- quiet: list, optional list of process names to suppress output for
- port: int, optional base port number for PORT environment variable
Returns:
List[ProcessParams]: list of process parameter objects with name, cmd, env, and quiet
"""
ProcessParams = namedtuple("ProcessParams", "name cmd quiet env")from honcho.environ import parse_procfile, Env
# Parse Procfile content
procfile_content = """
web: python app.py
worker: python worker.py
scheduler: python scheduler.py
"""
procfile = parse_procfile(procfile_content)
print(procfile.processes)
# OrderedDict([('web', 'python app.py'), ('worker', 'python worker.py'), ('scheduler', 'python scheduler.py')])from honcho.environ import Env
# Configuration typically comes from command-line args, env vars, and defaults
config = {
'app_root': '/path/to/app',
'procfile': 'Procfile',
'port': '5000'
}
env = Env(config)
# Load Procfile
try:
procfile = env.load_procfile()
print(f"Found {len(procfile.processes)} processes")
except IOError:
print("Procfile not found")
# Get port configuration
try:
port = env.port
print(f"Port: {port}")
except ValueError as e:
print(f"Invalid port: {e}")from honcho.environ import parse
# Parse .env file content
env_content = """
DATABASE_URL=postgresql://localhost:5432/mydb
DEBUG=true
SECRET_KEY=my-secret-key
PORT=8000
"""
env_vars = parse(env_content)
print(env_vars)
# {'DATABASE_URL': 'postgresql://localhost:5432/mydb', 'DEBUG': 'true', 'SECRET_KEY': 'my-secret-key', 'PORT': '8000'}from honcho.environ import expand_processes, parse_procfile
# Parse Procfile
procfile_content = """
web: python app.py
worker: python worker.py
"""
procfile = parse_procfile(procfile_content)
# Expand processes with concurrency and port assignment
concurrency = {'web': 2, 'worker': 3}
env_vars = {'DATABASE_URL': 'postgresql://localhost:5432/mydb'}
base_port = 5000
expanded = expand_processes(
procfile.processes,
concurrency=concurrency,
env=env_vars,
port=base_port
)
for process in expanded:
print(f"Name: {process.name}")
print(f"Command: {process.cmd}")
print(f"Environment: {process.env}")
print(f"Quiet: {process.quiet}")
print("---")
# Output:
# Name: web.1
# Command: python app.py
# Environment: {'DATABASE_URL': 'postgresql://localhost:5432/mydb', 'HONCHO_PROCESS_NAME': 'web.1', 'PORT': '5000'}
# Quiet: False
# ---
# Name: web.2
# Command: python app.py
# Environment: {'DATABASE_URL': 'postgresql://localhost:5432/mydb', 'HONCHO_PROCESS_NAME': 'web.2', 'PORT': '5001'}
# Quiet: False
# ---
# Name: worker.1, worker.2, worker.3...from collections import ChainMap
import os
from honcho.environ import Env
# Typical configuration chaining from CLI args, env files, OS env, and defaults
defaults = {'port': '5000', 'procfile': 'Procfile'}
os_env = {'PORT': os.environ.get('PORT')}
env_file = {'DATABASE_URL': 'postgresql://localhost:5432/mydb'}
cli_args = {'app_root': '/path/to/app'}
# Create configuration chain (first match wins)
config = ChainMap(cli_args, env_file, os_env, defaults)
# Create environment manager
env = Env(config)import re
# Procfile line pattern for parsing
PROCFILE_LINE = re.compile(r'^([A-Za-z0-9_-]+):\s*(.+)$')Install with Tessl CLI
npx tessl i tessl/pypi-honcho