Building CLI Applications¶
Introduction¶
The appcore.cli module provides a comprehensive framework for building
command-line applications that integrate seamlessly with appcore’s
configuration and initialization systems. This guide demonstrates practical
patterns for creating CLIs with multiple output formats, flexible stream
routing, and rich presentation capabilities.
Using the CLI Module¶
Basic Command Structure¶
CLI applications in appcore follow a consistent pattern using Command and
Application base classes:
from appcore import cli, state, __
import asyncio
class GreetCommand( cli.Command ):
async def execute( self, auxdata: state.Globals ) -> None:
if not isinstance( auxdata, GreetGlobals ):
raise TypeError( "GreetCommand requires GreetGlobals" )
print( f"Hello, {auxdata.username}!" )
class GreetGlobals( state.Globals ):
username: str
class GreetApplication( cli.Application ):
username: str = "World"
async def execute( self, auxdata: state.Globals ) -> None:
command = GreetCommand( )
await command.execute( auxdata )
async def prepare( self, exits ) -> state.Globals:
auxdata_base = await super( ).prepare( exits )
nomargs = {
field.name: getattr( auxdata_base, field.name )
for field in __.dcls.fields( auxdata_base )
if not field.name.startswith( '_' ) }
return GreetGlobals( username = self.username, **nomargs )
Commands use isinstance() type guards to safely access specialized data
while maintaining protocol compliance.
Display Options and Output Control¶
The DisplayOptions class provides standardized output configuration:
from appcore import cli, state
import json
class DataGlobals( state.Globals ):
display: cli.DisplayOptions
class ShowDataCommand( cli.Command ):
async def execute( self, auxdata: state.Globals ) -> None:
if not isinstance( auxdata, DataGlobals ):
raise TypeError( "ShowDataCommand requires DataGlobals" )
data = {
"application": auxdata.application.name,
"platform": auxdata.directories.user_data_path.as_posix( ),
"config_keys": list( auxdata.configuration.keys( ) )
}
await auxdata.display.render( data )
class DataApplication( cli.Application ):
display: cli.DisplayOptions = cli.DisplayOptions( )
async def execute( self, auxdata: state.Globals ) -> None:
command = ShowDataCommand( )
await command.execute( auxdata )
async def prepare( self, exits ) -> state.Globals:
auxdata_base = await super( ).prepare( exits )
nomargs = {
field.name: getattr( auxdata_base, field.name )
for field in __.dcls.fields( auxdata_base )
if not field.name.startswith( '_' ) }
return DataGlobals( display = self.display, **nomargs )
The display.render() method automatically handles multiple output formats
based on the presentation mode.
Stream Routing and File Output¶
Commands can route output to different streams or files:
from appcore import cli, state
import asyncio
from pathlib import Path
class LoggingGlobals( state.Globals ):
display: cli.DisplayOptions
class DiagnosticCommand( cli.Command ):
async def execute( self, auxdata: state.Globals ) -> None:
if not isinstance( auxdata, LoggingGlobals ):
raise TypeError( "DiagnosticCommand requires LoggingGlobals" )
diagnostic_data = {
"memory_usage": "45MB",
"active_connections": 12,
"cache_hits": 234
}
await auxdata.display.render( diagnostic_data )
class DiagnosticApplication( cli.Application ):
display: cli.DisplayOptions = cli.DisplayOptions( )
async def execute( self, auxdata: state.Globals ) -> None:
command = DiagnosticCommand( )
enriched_auxdata = LoggingGlobals(
display = self.display,
**auxdata.__dict__
)
await command.execute( enriched_auxdata )
Users can control output destination through command-line arguments:
# Output to stdout (default)
python -m myapp diagnostic
# Output to stderr
python -m myapp --display.target-stream stderr diagnostic
# Output to file
python -m myapp --display.target-file report.json diagnostic
Subcommands and Complex Applications¶
For applications with multiple commands, use tyro’s subcommand annotations:
from appcore import cli, state
import tyro
from typing import Union, Annotated
class StatusGlobals( state.Globals ):
display: cli.DisplayOptions
class StatusCommand( cli.Command ):
async def execute( self, auxdata: state.Globals ) -> None:
if not isinstance( auxdata, StatusGlobals ):
raise TypeError( "StatusCommand requires StatusGlobals" )
status_info = {"status": "running", "uptime": "2h 34m"}
await auxdata.display.render( status_info )
class StatsCommand( cli.Command ):
async def execute( self, auxdata: state.Globals ) -> None:
if not isinstance( auxdata, StatusGlobals ):
raise TypeError( "StatsCommand requires StatusGlobals" )
stats_info = {"requests": 1542, "errors": 3}
await auxdata.display.render( stats_info )
class MonitorApplication( cli.Application ):
display: cli.DisplayOptions = cli.DisplayOptions( )
command: Union[
Annotated[
StatusCommand,
tyro.conf.subcommand( "status", prefix_name = False ),
],
Annotated[
StatsCommand,
tyro.conf.subcommand( "stats", prefix_name = False ),
],
] = StatusCommand( )
async def execute( self, auxdata: state.Globals ) -> None:
enriched_auxdata = StatusGlobals(
display = self.display, **auxdata.__dict__ )
await self.command.execute( enriched_auxdata )
This creates a CLI with subcommands accessible as python -m myapp status and python -m myapp stats.
Demonstration: The appcore CLI¶
Overview¶
The built-in appcore CLI tool demonstrates all the patterns described above.
It provides introspection capabilities for configuration, environment variables,
and platform directories, showcasing practical CLI implementation techniques.
Configuration Introspection¶
View your application’s merged configuration from TOML files:
# Default rich format with syntax highlighting
python -m appcore configuration
# JSON format for programmatic consumption
python -m appcore configuration --display.presentation json
# TOML format matching input files
python -m appcore configuration --display.presentation toml
# Plain text format for simple parsing
python -m appcore configuration --display.presentation plain
The configuration command shows the final merged configuration after processing all TOML files, includes, and template variable substitution.
Environment Variable Inspection¶
Show application-specific environment variables:
# Show all APPCORE_* environment variables
python -m appcore environment
# JSON format for scripting
python -m appcore environment --display.presentation json
# Save to file for later analysis
python -m appcore environment --display.target-file env-vars.json
The environment command filters environment variables by application name prefix, showing only variables that affect your application’s behavior.
Platform Directory Discovery¶
Display platform-specific directories for data, configuration, and caching:
# Rich format showing directory paths with labels
python -m appcore directories
# Save directory paths to file for scripts
python -m appcore directories --display.target-file dirs.txt
# JSON format with full path information
python -m appcore directories --display.presentation json
The directories command shows where your application should store different types of data according to platform conventions.
Advanced Output Options¶
Combine presentation formats with output routing for complex scenarios:
# Separate main output and logging streams
python -m appcore configuration --display.target-stream stdout --inscription.target-stream stderr
# Save both output and logs to files
python -m appcore configuration --display.target-file config.json --inscription.target-file app.log
# Rich output with colorization control
python -m appcore configuration --display.no-colorize --display.presentation rich
# Force rich terminal capabilities for testing
python -m appcore configuration --display.assume-rich-terminal --display.presentation rich
These options provide precise control over where different types of output are directed, enabling integration with shell scripts and monitoring systems.
Configuration File Integration¶
The appcore CLI integrates with configuration files like any appcore application:
# Use specific configuration file
python -m appcore --configfile /path/to/config.toml configuration
# Disable environment loading
python -m appcore --no-environment configuration
Configuration files can specify default presentation formats, output locations, and logging levels that the CLI will respect.
Implementation Reference¶
The complete implementation can be found in sources/appcore/introspection.py,
which demonstrates:
Advanced subcommand patterns with tyro annotations
Custom DisplayOptions subclasses with additional presentation formats
Integration between CLI arguments and appcore preparation systems
Type-safe command implementations using isinstance guards
Rich terminal detection and colorization handling
Stream and file output management with proper resource cleanup
This serves as a comprehensive reference for building production CLI applications with similar capabilities and patterns.