CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cement

Advanced Application Framework for Python with a focus on Command Line Interfaces

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

controllers.mddocs/

Controllers and Commands

The controller system provides command organization and sub-command functionality using an argparse-based framework. Controllers enable organizing CLI functionality into logical groups with nested commands and parameter handling.

Capabilities

Controller Base Class

Base controller class that all application controllers should inherit from. Provides the foundation for command organization and argument handling.

class Controller:
    """
    Base controller class for organizing CLI commands.
    
    Controllers group related commands together and provide a structured
    way to organize application functionality. Each controller can have
    multiple commands exposed through decorated methods.
    """
    
    def __init__(self, **kw: Any) -> None:
        """
        Initialize the controller.
        
        Args:
            **kw: Additional keyword arguments
        """
    
    @property
    def _meta(self) -> Any:
        """Controller meta-data configuration object."""

Expose Decorator

Decorator for exposing controller methods as CLI commands. The exposed methods become available as sub-commands when the application runs.

def expose(
    hide: bool = False, 
    arguments: List[ArgparseArgumentType] = [], 
    label: str = None, 
    **parser_options: Any
) -> Callable:
    """
    Decorator that exposes controller methods as CLI commands.
    
    Args:
        hide: Whether to hide the command from help output
        arguments: List of argparse argument definitions  
        label: Override command name (defaults to method name)
        **parser_options: Additional argparse subparser options (help, aliases, etc.)
        
    Returns:
        Decorated function that becomes a CLI command
    """

# Alias for expose decorator
ex = expose

Controller Meta Configuration

Controller behavior is controlled through the Meta class which defines controller properties and command handling options.

class Meta:
    """
    Controller meta-data configuration.
    
    Controls controller behavior including label, stacked commands,
    and argument handling.
    """
    
    label: str = None
    """Controller label/name"""
    
    stacked_on: str = None
    """Stack this controller on top of another controller"""
    
    stacked_type: str = 'embedded'
    """Stacking type: 'embedded' or 'nested'"""
    
    description: str = None
    """Controller description for help text"""
    
    usage: str = None
    """Custom usage string for help text"""
    
    help: str = None
    """Help text for the controller"""
    
    aliases: List[str] = []
    """List of controller aliases"""
    
    arguments: List[Tuple[List[str], Dict[str, Any]]] = []
    """List of controller-level arguments"""

Usage Examples

Basic Controller

from cement import App, Controller, ex

class BaseController(Controller):
    class Meta:
        label = 'base'
        description = 'Base controller for application commands'
    
    @ex(help='display application information')
    def info(self):
        """Display application information."""
        print('MyApp v1.0.0')
        print('A sample cement application')
    
    @ex(
        help='greet someone',
        arguments=[
            (['name'], {'help': 'name to greet'}),
            (['--uppercase', '-u'], {
                'help': 'print in uppercase',
                'action': 'store_true'
            })
        ]
    )
    def greet(self):
        """Greet someone by name."""
        name = self.app.pargs.name
        greeting = f'Hello {name}!'
        
        if self.app.pargs.uppercase:
            greeting = greeting.upper()
        
        print(greeting)

class MyApp(App):
    class Meta:
        label = 'myapp'
        base_controller = 'base'
        handlers = [BaseController]

with MyApp() as app:
    app.run()

Stacked Controllers

from cement import App, Controller, ex

class BaseController(Controller):
    class Meta:
        label = 'base'
    
    @ex(help='base command')
    def default(self):
        print('Base controller default command')

class DatabaseController(Controller):
    class Meta:
        label = 'database'
        stacked_on = 'base'
        stacked_type = 'nested'
        description = 'Database management commands'
    
    @ex(help='initialize database')
    def init(self):
        print('Initializing database...')
    
    @ex(help='migrate database')
    def migrate(self):
        print('Running migrations...')

class UserController(Controller):
    class Meta:
        label = 'user'
        stacked_on = 'base'
        stacked_type = 'nested'
        description = 'User management commands'
    
    @ex(
        help='create a new user',
        arguments=[
            (['username'], {'help': 'username for new user'}),
            (['--email'], {'help': 'email address'})
        ]
    )
    def create(self):
        username = self.app.pargs.username
        email = getattr(self.app.pargs, 'email', None)
        print(f'Creating user: {username}')
        if email:
            print(f'Email: {email}')

class MyApp(App):
    class Meta:
        label = 'myapp'
        base_controller = 'base'
        handlers = [
            BaseController,
            DatabaseController, 
            UserController
        ]

with MyApp() as app:
    app.run()

# Usage:
# myapp info                    # Base controller
# myapp database init           # Database controller  
# myapp database migrate        # Database controller
# myapp user create john        # User controller
# myapp user create jane --email jane@example.com

Controller with Arguments

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 command when no sub-command is 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 file: {self.app.pargs.config_file}')
        
        print('Default command executed')
    
    @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'
            })
        ]
    )
    def process(self):
        """Process multiple files."""
        files = self.app.pargs.files
        output_dir = self.app.pargs.output
        format_type = self.app.pargs.format
        
        print(f'Processing {len(files)} files')
        print(f'Output directory: {output_dir}')
        print(f'Output format: {format_type}')
        
        for file_path in files:
            print(f'Processing: {file_path}')

class MyApp(App):
    class Meta:
        label = 'myapp'
        base_controller = 'base'
        handlers = [BaseController]

with MyApp() as app:
    app.run()

# Usage:
# myapp --verbose
# myapp --config /etc/myapp.conf
# myapp process file1.txt file2.txt --output /tmp --format yaml

Command Aliases

from cement import App, Controller, ex

class BaseController(Controller):
    class Meta:
        label = 'base'
    
    @ex(
        help='display status information',
        aliases=['stat', 'st']
    )
    def status(self):
        """Display application status."""
        print('Application is running')
    
    @ex(
        help='restart the application',
        aliases=['reboot', 'reload']
    )
    def restart(self):
        """Restart the application."""
        print('Restarting application...')

class MyApp(App):
    class Meta:
        label = 'myapp'
        base_controller = 'base'
        handlers = [BaseController]

with MyApp() as app:
    app.run()

# All of these commands are equivalent:
# myapp status
# myapp stat  
# myapp st

Hidden Commands

from cement import App, Controller, ex

class BaseController(Controller):
    class Meta:
        label = 'base'
    
    @ex(help='public command')
    def public_cmd(self):
        """This command appears in help."""
        print('Public command executed')
    
    @ex(
        help='hidden command for debugging',
        hide=True
    )
    def debug_cmd(self):
        """This command is hidden from help but still callable."""
        print('Debug command executed')

class MyApp(App):
    class Meta:
        label = 'myapp'
        base_controller = 'base'
        handlers = [BaseController]

with MyApp() as app:
    app.run()
    
# myapp debug_cmd still works, but won't appear in --help

Accessing Application Context

from cement import App, Controller, ex

class BaseController(Controller):
    class Meta:
        label = 'base'
    
    @ex(help='demonstrate app context access')
    def demo(self):
        """Demonstrate accessing application context."""
        # Access application configuration
        debug_mode = self.app.config.get('myapp', 'debug')
        print(f'Debug mode: {debug_mode}')
        
        # Access logging
        self.app.log.info('Demo command executed')
        
        # Access parsed arguments
        if hasattr(self.app.pargs, 'verbose'):
            print(f'Verbose: {self.app.pargs.verbose}')
        
        # Render output using output handler
        data = {'message': 'Hello from demo command', 'timestamp': '2023-01-01'}
        output = self.app.render(data)
        print(output)

class MyApp(App):
    class Meta:
        label = 'myapp'
        base_controller = 'base'
        handlers = [BaseController]

with MyApp() as app:
    app.run()

Install with Tessl CLI

npx tessl i tessl/pypi-cement

docs

arguments.md

caching.md

configuration.md

controllers.md

extensions.md

foundation.md

hooks.md

index.md

interface-handler.md

logging.md

mail.md

output.md

plugins.md

templates.md

utilities.md

tile.json