Basic Usage¶
Introduction¶
The appcore
package provides a simple async initialization framework for
applications that need configuration loading, platform directory management,
and environment setup. The core function is prepare()
, which returns a
Globals
object containing all the initialized application state.
Basic Application Setup¶
The simplest way to initialize appcore for your application:
import asyncio
import contextlib
import appcore
async def main( ):
async with contextlib.AsyncExitStack( ) as exits:
# Initialize application core with sensible defaults
globals_dto = await appcore.prepare( exits )
# Access application information
print( f"Application: {globals_dto.application.name}" )
print( f"Publisher: {globals_dto.application.publisher}" )
print( f"Version: {globals_dto.application.version}" )
# Check if running in development mode
if globals_dto.distribution.editable:
print( 'Running in development mode' )
else:
print( 'Running in production mode' )
if __name__ == '__main__':
asyncio.run( main( ) )
This will automatically detect your application name from the package, set up platform directories, load configuration, and prepare logging.
Platform Directory Access¶
The Globals
object provides convenient access to platform-specific
directories for your application:
import asyncio
import contextlib
import appcore
from pathlib import Path
async def main( ):
async with contextlib.AsyncExitStack( ) as exits:
globals_dto = await appcore.prepare( exits )
# Get platform directories for data storage
cache_dir = globals_dto.provide_cache_location( )
data_dir = globals_dto.provide_data_location( )
state_dir = globals_dto.provide_state_location( )
print( f"Cache directory: {cache_dir}" )
print( f"Data directory: {data_dir}" )
print( f"State directory: {state_dir}" )
# Create subdirectories for organization
logs_dir = globals_dto.provide_data_location( 'logs' )
config_dir = globals_dto.provide_data_location( 'config' )
print( f"Logs will go in: {logs_dir}" )
print( f"Config files in: {config_dir}" )
# Directories are automatically created as needed
logs_dir.mkdir( parents = True, exist_ok = True )
config_dir.mkdir( parents = True, exist_ok = True )
if __name__ == '__main__':
asyncio.run( main( ) )
The exact paths depend on your operating system:
Linux:
~/.cache/appname
,~/.local/share/appname
,~/.local/state/appname
macOS:
~/Library/Caches/appname
,~/Library/Application Support/appname
, etc.Windows:
%LOCALAPPDATA%\appname
,%APPDATA%\appname
, etc.
Working with Application Data¶
You can store and retrieve application data using the provided directories:
import asyncio
import contextlib
import json
import appcore
async def save_user_preferences( globals_dto, preferences ):
''' Save user preferences to the data directory. '''
prefs_file = globals_dto.provide_data_location( 'preferences.json' )
prefs_file.parent.mkdir( parents = True, exist_ok = True )
with open( prefs_file, 'w' ) as f:
json.dump( preferences, f, indent = 2 )
print( f"Preferences saved to: {prefs_file}" )
async def load_user_preferences( globals_dto ):
''' Load user preferences from the data directory. '''
prefs_file = globals_dto.provide_data_location( 'preferences.json' )
if prefs_file.exists( ):
with open( prefs_file, 'r' ) as f:
preferences = json.load( f )
print( f"Loaded preferences: {preferences}" )
return preferences
else:
print( 'No preferences file found, using defaults' )
return { 'theme': 'dark', 'auto_save': True }
async def main( ):
async with contextlib.AsyncExitStack( ) as exits:
globals_dto = await appcore.prepare( exits )
# Save some example preferences
prefs = { 'theme': 'light', 'auto_save': False, 'recent_files': [ ] }
await save_user_preferences( globals_dto, prefs )
# Load them back
loaded_prefs = await load_user_preferences( globals_dto )
if __name__ == '__main__':
asyncio.run( main( ) )
Custom Application Information¶
You can customize the application metadata used for directory generation:
import asyncio
import contextlib
import appcore
async def main( ):
# Create custom application information
app_info = appcore.ApplicationInformation(
name = 'my-awesome-app',
publisher = 'MyCompany',
version = '2.1.0'
)
async with contextlib.AsyncExitStack( ) as exits:
# Use custom application info
globals_dto = await appcore.prepare(
exits,
application = app_info
)
print( f"App: {globals_dto.application.name}" )
print( f"Publisher: {globals_dto.application.publisher}" )
print( f"Data dir: {globals_dto.provide_data_location()}" )
# The directories will include publisher and version info:
# Linux: ~/.local/share/MyCompany/my-awesome-app/2.1.0/
# macOS: ~/Library/Application Support/MyCompany/my-awesome-app/2.1.0/
if __name__ == '__main__':
asyncio.run( main( ) )
Error Handling¶
The prepare()
function can raise exceptions in certain scenarios. Use Omnierror
to catch all package errors, or specific exceptions for fine-grained handling:
import asyncio
import contextlib
import appcore
async def main( ):
try:
async with contextlib.AsyncExitStack( ) as exits:
globals_dto = await appcore.prepare( exits )
print( 'Initialization successful!' )
except appcore.exceptions.Omnierror as e:
print( f"Appcore initialization error: {e}" )
# Omnierror catches all errors from the package API
# You can also catch specific exceptions for fine-grained handling
except Exception as e:
print( f"Unexpected system error: {e}" )
raise
if __name__ == '__main__':
asyncio.run( main( ) )
For fine-grained error handling, catch specific exceptions:
try:
async with contextlib.AsyncExitStack( ) as exits:
globals_dto = await appcore.prepare( exits )
except appcore.exceptions.FileLocateFailure as e:
print( f"Could not locate required files: {e}" )
# This can happen in development if pyproject.toml is not found
except appcore.exceptions.OperationInvalidity as e:
print( f"Invalid operation during setup: {e}" )
# This can happen with malformed configuration files
Logging Configuration¶
The appcore
package provides flexible logging configuration through the
inscription
module. Logging is automatically configured during prepare()
with sensible defaults, but can be customized for different environments.
Basic Logging Setup¶
Logging is configured automatically with the default settings:
import asyncio
import contextlib
import logging
import appcore
async def main( ):
async with contextlib.AsyncExitStack( ) as exits:
# Logging is configured automatically with defaults
globals_dto = await appcore.prepare( exits )
# Use standard Python logging
logger = logging.getLogger( __name__ )
logger.info( 'Application started successfully' )
logger.debug( 'Debug information' )
logger.warning( 'Warning message' )
logger.error( 'Error occurred' )
if __name__ == '__main__':
asyncio.run( main( ) )
The default configuration uses:
Mode: Plain (standard Python logging)
Level: Info (shows info, warning, error, critical)
Target: Standard error stream
Custom Logging Configuration¶
You can customize logging behavior using inscription.Control
:
import asyncio
import contextlib
import logging
import sys
import appcore
async def main( ):
# Create custom inscription control
inscription_control = appcore.inscription.Control(
mode = appcore.inscription.Modes.Rich, # Enhanced formatting
level = 'debug', # Show debug messages
target = sys.stdout # Log to stdout instead of stderr
)
async with contextlib.AsyncExitStack( ) as exits:
globals_dto = await appcore.prepare(
exits,
inscription = inscription_control
)
# Test different log levels
logger = logging.getLogger( __name__ )
logger.debug( 'Debug: Application initialized' )
logger.info( 'Info: Processing started' )
logger.warning( 'Warning: Non-critical issue detected' )
logger.error( 'Error: Something went wrong' )
if __name__ == '__main__':
asyncio.run( main( ) )
Logging Modes¶
The inscription module supports three different modes:
Null Mode - Minimal logging setup, defers to external management:
inscription_control = appcore.inscription.Control(
mode = appcore.inscription.Modes.Null
)
Plain Mode - Standard Python logging (default):
inscription_control = appcore.inscription.Control(
mode = appcore.inscription.Modes.Plain,
level = 'info'
)
Rich Mode - Enhanced formatting with colors and better tracebacks:
inscription_control = appcore.inscription.Control(
mode = appcore.inscription.Modes.Rich,
level = 'debug'
)
Rich mode automatically falls back to plain mode if the rich
package is not installed.
Environment Variable Configuration¶
Logging levels can be controlled through environment variables without code changes:
# Set inscription level specifically (using your application name)
export MYAPP_INSCRIPTION_LEVEL=debug
# Or use the general log level variable
export MYAPP_LOG_LEVEL=warning
import asyncio
import contextlib
import logging
import appcore
async def main( ):
async with contextlib.AsyncExitStack( ) as exits:
# Environment variables automatically override configured level
globals_dto = await appcore.prepare( exits )
logger = logging.getLogger( __name__ )
logger.debug( 'This appears if MYAPP_INSCRIPTION_LEVEL=debug' )
logger.info( 'This appears if level is info or lower' )
logger.warning( 'This appears if level is warning or lower' )
if __name__ == '__main__':
asyncio.run( main( ) )
Environment variable precedence:
{APPLICATION_NAME}_INSCRIPTION_LEVEL
- Inscription-specific level (highest priority){APPLICATION_NAME}_LOG_LEVEL
- General log levelConfiguration in code (lowest priority)
Where {APPLICATION_NAME}
is your application’s name in uppercase (e.g., MYAPP_INSCRIPTION_LEVEL
for an application named “myapp”).
Custom Log Targets¶
You can redirect logs to custom streams or files:
import asyncio
import contextlib
import logging
import io
import appcore
async def main( ):
# Create custom output stream
log_stream = io.StringIO( )
inscription_control = appcore.inscription.Control(
mode = appcore.inscription.Modes.Plain,
level = 'info',
target = log_stream
)
async with contextlib.AsyncExitStack( ) as exits:
globals_dto = await appcore.prepare(
exits,
inscription = inscription_control
)
# Log some messages
logger = logging.getLogger( __name__ )
logger.info( 'Message 1' )
logger.info( 'Message 2' )
logger.warning( 'Warning message' )
# Retrieve captured logs
log_output = log_stream.getvalue( )
print( f"Captured logs:\n{log_output}" )
if __name__ == '__main__':
asyncio.run( main( ) )
This is particularly useful for testing or when you need to capture and process log output programmatically.
Application Profiles for Different Deployments¶
Create application information objects for different deployment scenarios:
>>> import appcore
>>> def create_application_profiles( ):
... ''' Create different application profiles. '''
... return {
... 'development': appcore.ApplicationInformation(
... name = 'myapp-dev',
... publisher = 'DevCorp',
... version = '0.1.0-dev'
... ),
... 'staging': appcore.ApplicationInformation(
... name = 'myapp-staging',
... publisher = 'DevCorp',
... version = '1.0.0-rc1'
... ),
... 'production': appcore.ApplicationInformation(
... name = 'myapp',
... publisher = 'ProductionCorp',
... version = '1.0.0'
... )
... }
>>>
>>> profiles = create_application_profiles( )
>>> dev_profile = profiles[ 'development' ]
>>> print( f"Dev app: {dev_profile.name} v{dev_profile.version}" )
Dev app: myapp-dev v0.1.0-dev
>>>
>>> # Each profile will have different platform directories
>>> dev_dirs = dev_profile.produce_platform_directories( )
>>> prod_dirs = profiles[ 'production' ].produce_platform_directories( )
>>> print( f"Different apps use different directories" )
Different apps use different directories
This pattern is useful for applications that need different configurations for development, staging, and production environments.
Next Steps¶
This covers the basic usage of appcore. For more advanced topics, see:
Configuration Management - Loading TOML configuration files with includes
Environment Handling - Development vs production detection and environment variables