Advanced Application Framework for Python with a focus on Command Line Interfaces
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
The argument parsing system provides command-line argument processing built on Python's argparse module. It offers unified argument handling that integrates with configuration management and controller systems.
Base interface for argument parsing functionality that defines the contract for command-line argument operations.
class ArgumentHandler:
"""
Argument handler interface for parsing command-line arguments.
Provides methods for adding arguments, parsing command-line input,
and integrating with the application configuration system.
"""
def add_argument(self, *args: Any, **kwargs: Any) -> None:
"""
Add a command-line argument.
Args:
*args: Positional arguments (e.g., '--verbose', '-v')
**kwargs: Keyword arguments (help, action, type, etc.)
"""
def parse(self, argv: List[str]) -> Any:
"""
Parse command-line arguments.
Args:
argv: List of command-line arguments to parse
Returns:
Parsed arguments object
"""from cement import App, Controller, ex
class BaseController(Controller):
class Meta:
label = 'base'
arguments = [
(['--verbose', '-v'], {
'help': 'verbose output',
'action': 'store_true'
}),
(['--config'], {
'help': 'configuration file path',
'dest': 'config_file'
})
]
def _default(self):
"""Default action when no sub-command specified."""
if self.app.pargs.verbose:
print('Verbose mode enabled')
if hasattr(self.app.pargs, 'config_file') and self.app.pargs.config_file:
print(f'Using config: {self.app.pargs.config_file}')
class MyApp(App):
class Meta:
label = 'myapp'
base_controller = 'base'
handlers = [BaseController]
with MyApp() as app:
app.run()
# Usage:
# myapp --verbose
# myapp --config /path/to/config.conffrom cement import App, Controller, ex
class FileController(Controller):
class Meta:
label = 'file'
stacked_on = 'base'
stacked_type = 'nested'
@ex(
help='process files',
arguments=[
(['files'], {
'nargs': '+',
'help': 'files to process'
}),
(['--output', '-o'], {
'help': 'output directory',
'default': './output'
}),
(['--format'], {
'choices': ['json', 'yaml', 'xml'],
'default': 'json',
'help': 'output format'
}),
(['--recursive', '-r'], {
'action': 'store_true',
'help': 'process directories recursively'
})
]
)
def process(self):
"""Process files with various options."""
files = self.app.pargs.files
output_dir = self.app.pargs.output
format_type = self.app.pargs.format
recursive = self.app.pargs.recursive
print(f'Processing {len(files)} files')
print(f'Output directory: {output_dir}')
print(f'Format: {format_type}')
print(f'Recursive: {recursive}')
for file_path in files:
print(f'Processing: {file_path}')
class BaseController(Controller):
class Meta:
label = 'base'
class MyApp(App):
class Meta:
label = 'myapp'
base_controller = 'base'
handlers = [BaseController, FileController]
with MyApp() as app:
app.run()
# Usage:
# myapp file process file1.txt file2.txt --format yaml --recursivefrom cement import App, Controller, ex
class BaseController(Controller):
class Meta:
label = 'base'
# Global arguments available to all commands
arguments = [
(['--debug'], {
'action': 'store_true',
'help': 'enable debug mode'
}),
(['--log-level'], {
'choices': ['DEBUG', 'INFO', 'WARNING', 'ERROR'],
'default': 'INFO',
'help': 'set logging level'
})
]
@ex(
help='show application status',
# Command-specific arguments
arguments=[
(['--detailed'], {
'action': 'store_true',
'help': 'show detailed status'
})
]
)
def status(self):
"""Show application status."""
# Access global arguments
debug = self.app.pargs.debug
log_level = self.app.pargs.log_level
# Access command-specific arguments
detailed = self.app.pargs.detailed
print(f'Debug mode: {debug}')
print(f'Log level: {log_level}')
if detailed:
print('Detailed status information...')
class MyApp(App):
class Meta:
label = 'myapp'
base_controller = 'base'
handlers = [BaseController]
with MyApp() as app:
app.run()
# Usage:
# myapp --debug --log-level DEBUG status --detailedfrom cement import App, Controller, ex
import os
def validate_file_exists(value):
"""Custom argument type that validates file exists."""
if not os.path.exists(value):
raise argparse.ArgumentTypeError(f"File '{value}' does not exist")
return value
def validate_positive_int(value):
"""Custom argument type that validates positive integers."""
try:
ivalue = int(value)
if ivalue <= 0:
raise argparse.ArgumentTypeError(f"'{value}' must be a positive integer")
return ivalue
except ValueError:
raise argparse.ArgumentTypeError(f"'{value}' is not a valid integer")
class BaseController(Controller):
class Meta:
label = 'base'
@ex(
help='backup file',
arguments=[
(['source'], {
'type': validate_file_exists,
'help': 'source file to backup'
}),
(['--destination'], {
'help': 'backup destination path'
}),
(['--max-backups'], {
'type': validate_positive_int,
'default': 5,
'help': 'maximum number of backups to keep'
})
]
)
def backup(self):
"""Backup a file with validation."""
source = self.app.pargs.source
destination = self.app.pargs.destination or f"{source}.bak"
max_backups = self.app.pargs.max_backups
print(f'Backing up {source} to {destination}')
print(f'Keeping maximum {max_backups} backups')
class MyApp(App):
class Meta:
label = 'myapp'
base_controller = 'base'
handlers = [BaseController]
with MyApp() as app:
app.run()
# Usage:
# myapp backup /path/to/file.txt --max-backups 10from cement import App, Controller, ex
import os
class BaseController(Controller):
class Meta:
label = 'base'
arguments = [
(['--database-url'], {
'help': 'database connection URL',
'default': os.getenv('DATABASE_URL', 'sqlite:///app.db'),
'dest': 'database_url'
}),
(['--api-key'], {
'help': 'API key for external service',
'default': os.getenv('API_KEY'),
'dest': 'api_key'
}),
(['--workers'], {
'type': int,
'help': 'number of worker processes',
'default': int(os.getenv('WORKERS', '4')),
'dest': 'worker_count'
})
]
@ex(help='show configuration')
def config(self):
"""Show current configuration from args/env."""
print(f'Database URL: {self.app.pargs.database_url}')
print(f'API Key: {"***" if self.app.pargs.api_key else "Not set"}')
print(f'Workers: {self.app.pargs.worker_count}')
class MyApp(App):
class Meta:
label = 'myapp'
base_controller = 'base'
handlers = [BaseController]
with MyApp() as app:
app.run()
# Environment variables take precedence:
# DATABASE_URL=postgresql://... myapp config
# myapp config --database-url mysql://...from cement import App, Controller, ex
import argparse
class DeployController(Controller):
class Meta:
label = 'deploy'
stacked_on = 'base'
stacked_type = 'nested'
@ex(
help='deploy application',
arguments=[
(['environment'], {
'choices': ['development', 'staging', 'production'],
'help': 'deployment environment'
}),
(['--version'], {
'help': 'application version to deploy'
}),
(['--config-file'], {
'action': 'append',
'help': 'configuration files (can be specified multiple times)'
}),
(['--dry-run'], {
'action': 'store_true',
'help': 'show what would be deployed without actually deploying'
}),
(['--force'], {
'action': 'store_true',
'help': 'force deployment even if validation fails'
}),
(['--exclude'], {
'action': 'append',
'help': 'exclude specific components from deployment'
}),
(['--timeout'], {
'type': int,
'default': 300,
'help': 'deployment timeout in seconds'
})
]
)
def start(self):
"""Start deployment process."""
env = self.app.pargs.environment
version = self.app.pargs.version or 'latest'
config_files = self.app.pargs.config_file or []
dry_run = self.app.pargs.dry_run
force = self.app.pargs.force
excludes = self.app.pargs.exclude or []
timeout = self.app.pargs.timeout
print(f'Deploying to {env} environment')
print(f'Version: {version}')
print(f'Config files: {config_files}')
print(f'Dry run: {dry_run}')
print(f'Force: {force}')
print(f'Excludes: {excludes}')
print(f'Timeout: {timeout}s')
class BaseController(Controller):
class Meta:
label = 'base'
class MyApp(App):
class Meta:
label = 'myapp'
base_controller = 'base'
handlers = [BaseController, DeployController]
with MyApp() as app:
app.run()
# Usage:
# myapp deploy production --version 1.2.3 --config-file prod.conf --config-file secrets.conf --exclude monitoring --dry-runInstall with Tessl CLI
npx tessl i tessl/pypi-cement