Skip to main content

Simple framework for command line driven applications in Python 3.7

Project description

spafw37

Python 3.7 License: MIT

A lightweight Python 3.7+ framework for building command-line applications with advanced configuration management, command orchestration, and execution control.

Repository: https://github.com/minouris/spafw37

Support Me: Support me on Patreon at Minouris Does Stuff! I'll post news and previews of all my projects, software and otherwise, including this one!

Features

  • Flexible Parameter System - Typed parameters with aliases, defaults, validation, persistence, complete lifecycle management (get/set/unset/reset), switch change behaviour control for mutually exclusive groups, and interactive prompts for user input
  • Declarative Command Definition - Define commands with actions, dependencies, and orchestration
  • Command Orchestration - Automatic dependency resolution, sequencing, and triggers
  • Multi-Phase Execution - Organize commands into setup, cleanup, execution, teardown, and end phases
  • Cycle Support - Repeating command sequences with init/loop/loop-end/finalisation hooks and top-level cycle registration (v1.1.0)
  • Configuration Management - Persistent and runtime configuration with file I/O
  • Integrated Logging - Built-in logging with levels, scopes, and file/console output
  • Automatic Help System - Generated help for commands, parameters, and groups

Installation

From PyPI (Production Releases)

pip install spafw37

From TestPyPI (Development Releases)

pip install -i https://test.pypi.org/simple/ spafw37

From Source

git clone https://github.com/minouris/spafw37.git
cd spafw37
python3 -m venv .venv
source .venv/bin/activate
pip install -e .

Development Installation

pip install -e .[dev]
pytest

Quick Start

Create a simple CLI application in minutes:

"""greet.py - A simple greeting application"""
from spafw37 import core as spafw37
from spafw37.constants.param import (
    PARAM_NAME,
    PARAM_DESCRIPTION,
    PARAM_ALIASES,
    PARAM_DEFAULT,
)
from spafw37.constants.command import (
    COMMAND_NAME,
    COMMAND_DESCRIPTION,
    COMMAND_ACTION,
)

# Define parameters
params = [
    {
        PARAM_NAME: 'name',
        PARAM_DESCRIPTION: 'Name to greet',
        PARAM_ALIASES: ['--name', '-n'],
        PARAM_DEFAULT: 'World',
    }
]

# Define command action
def greet():
    name = spafw37.get_param('name')
    print(f"Hello, {name}!")

# Define commands
commands = [
    {
        COMMAND_NAME: 'greet',
        COMMAND_DESCRIPTION: 'Greet someone',
        COMMAND_ACTION: greet,
    }
]

# Register and run
spafw37.add_params(params)
spafw37.add_commands(commands)

if __name__ == '__main__':
    spafw37.run_cli()

Run it:

python greet.py greet --name Alice
# Output: Hello, Alice!

python greet.py greet
# Output: Hello, World!

python greet.py --help
# Shows available commands and parameters

Documentation

Comprehensive guides are available in the doc/ directory:

Core Concepts

Parameters

Define parameters as dictionaries with type information, aliases, and behavior:

from spafw37.constants.param import *

{
    PARAM_NAME: 'output-dir',
    PARAM_DESCRIPTION: 'Output directory',
    PARAM_ALIASES: ['--output', '-o'],
    PARAM_TYPE: PARAM_TYPE_TEXT,
    PARAM_DEFAULT: './output',
    PARAM_REQUIRED: False,
}

Types: PARAM_TYPE_TEXT, PARAM_TYPE_NUMBER, PARAM_TYPE_TOGGLE, PARAM_TYPE_LIST, PARAM_TYPE_DICT

Examples:

Interactive Prompts

Automatically prompt users for missing parameter values at runtime:

from spafw37.constants.param import *

{
    PARAM_NAME: 'api-key',
    PARAM_PROMPT: 'Enter API key: ',
    PARAM_PROMPT_ON: PROMPT_ON_START,      # Prompt before command execution
    PARAM_PROMPT_SENSITIVE: True,          # Hide input for passwords/tokens
}

Prompt Timing:

  • PROMPT_ON_START - Prompt once before any commands execute
  • PROMPT_ON_COMMAND - Prompt before each command that needs the value
  • PROMPT_NEVER - Never prompt (default)

Examples:

Commands

Define commands with actions, dependencies, and execution control:

from spafw37.constants.command import *

{
    COMMAND_NAME: 'deploy',
    COMMAND_DESCRIPTION: 'Deploy application',
    COMMAND_ACTION: deploy_function,
    COMMAND_REQUIRED_PARAMS: ['environment'],
    COMMAND_REQUIRE_BEFORE: ['build', 'test'],
    COMMAND_PHASE: PHASE_EXECUTION,
}

Examples:

Phases

Commands execute in phases (setup → cleanup → execution → teardown → end):

from spafw37.constants.phase import *

spafw37.set_phases_order([
    PHASE_SETUP,
    PHASE_CLEANUP,
    PHASE_EXECUTION,
    PHASE_TEARDOWN,
    PHASE_END,
])

Examples:

Cycles

Repeat command sequences with loop control:

from spafw37.constants.cycle import *

{
    COMMAND_NAME: 'process-all',
    COMMAND_CYCLE: {
        CYCLE_INIT: init_processing,
        CYCLE_LOOP: has_more_items,
        CYCLE_LOOP_START: prepare_next_item,
        CYCLE_LOOP_END: cleanup_item,
        CYCLE_END: finalize_processing,
        CYCLE_COMMANDS: ['validate', 'transform', 'save'],
    }
}

Examples:

Configuration

Access parameter values in your command actions:

def my_command():
    # Get parameter values with automatic type coercion
    name = spafw37.get_param('name')        # Returns value based on PARAM_TYPE
    count = spafw37.get_param('count')      # Int if PARAM_TYPE_NUMBER
    enabled = spafw37.get_param('enabled')  # Bool if PARAM_TYPE_TOGGLE
    items = spafw37.get_param('items')      # List if PARAM_TYPE_LIST
    config = spafw37.get_param('config')    # Dict if PARAM_TYPE_DICT
    
    # Set parameter values
    spafw37.set_param('status', 'processing')
    
    # Accumulate values (for lists, dicts, strings)
    spafw37.join_param('tags', 'new-tag')
    
    # Unset parameter (return to default or None)
    spafw37.unset_param('status')           # Removes current value
    
    # Reset parameter (clear and return to default)
    spafw37.reset_param('count')            # Restores PARAM_DEFAULT value

Parameters can be:

  • Set via command-line parameters
  • Loaded from persistent config files (config.json)
  • Saved to user config files (--save-config, --load-config)
  • Managed at runtime within commands
  • Protected with PARAM_IMMUTABLE: True for write-once behavior (prevents modification after first set)

Examples:

Logging

Built-in logging system with multiple levels:

from spafw37 import core as spafw37

spafw37.log_trace('scope', 'Detailed trace information')
spafw37.log_debug('scope', 'Debug information')
spafw37.log_info('scope', 'General information')
spafw37.log_warning('scope', 'Warning message')
spafw37.log_error('scope', 'Error message')

Control logging via built-in parameters:

  • --no-logging - Disable all logging
  • --verbose - Enable verbose output
  • --log-dir - Specify log directory

Inline Definitions

Define parameters and commands inline without separate dictionaries:

Examples:

Output Handling

Custom output formatting and handlers:

Examples:


See the Examples README for detailed descriptions and usage instructions.

Testing

Run the test suite:

pytest tests/ -v --cov=spafw37 --cov-report=term-missing

Requires 80% test coverage to pass.

Requirements

  • Python 3.7 or higher
  • No external dependencies for core functionality
  • Development dependencies: pytest, pytest-cov, typing_extensions

Python 3.7 Compatibility

This framework is specifically designed for Python 3.7.x<=9 compatibility, largely to make it of better use to the Sims 4 modding community.

What's New in v1.1.0

  • Top-Level Cycle Registration - New add_cycle() and add_cycles() functions enable defining cycles separately from commands. Cycles can be registered before or after commands, providing better code organisation and flexible definition order. Inline cycle definitions via COMMAND_CYCLE remain fully supported.
  • Interactive Prompts - New PARAM_PROMPT constant enables interactive user input for parameters that are unset. Control prompt timing with PARAM_PROMPT_ON (start, per-command, or never), configure repeated prompts with PARAM_PROMPT_REPEAT for cycle iterations, and hide sensitive input with PARAM_PROMPT_SENSITIVE. Supports custom prompt handlers, validation with retries, and can be overridden via CLI arguments.
  • Switch Change Behaviour Control - New PARAM_SWITCH_CHANGE_BEHAVIOR constant controls what happens when setting parameters in mutually exclusive groups: SWITCH_UNSET (automatically clear conflicting switches), SWITCH_RESET (restore defaults), or SWITCH_REJECT (raise error - default). Enables mode switching patterns and state restoration while maintaining backward compatibility.
  • Allowed Values Validation - New PARAM_ALLOWED_VALUES constant restricts TEXT, NUMBER, and LIST parameters to predefined value sets. TEXT and LIST parameters use case-insensitive matching with automatic normalisation to canonical case. Provides clear error messages when invalid values are provided.
  • New Parameter API - Data is now shared, validated and accessed via parameters instead of directly accessing config, with get_param(), set_param() and join_param() providing validated and typed access to parameter values. Customisable value parsers allow for more flexible handling of values on the command line.
  • Parameter Lifecycle Management - Complete lifecycle control with unset_param() to remove values and reset_param() to restore defaults, plus PARAM_IMMUTABLE for write-once protection.
  • Param-Focused Architecture - Parameters are now the primary abstraction, with direct access to config being deprecated.
  • Cycle Loop End Hook - New CYCLE_LOOP_END constant for per-iteration cleanup, counter increments, and result accumulation after cycle commands complete.

Known Issues

Thread Safety

This framework is designed for single-threaded CLI applications. Module-level state variables in command.py, cycle.py, param.py, and config.py are not thread-safe. If you need to use this framework in a multi-threaded context, you must implement external synchronization around framework operations.

Reporting Bugs

Found a bug? Please report it on our GitHub Issues page.

When reporting bugs, please include:

  • Python version (python --version)
  • spafw37 version
  • Minimal code to reproduce the issue
  • Expected vs actual behavior
  • Full error traceback (if applicable)

For feature requests and questions, also use GitHub Issues with the appropriate label.

Author

Minouris (Ciara Norrish) (@minouris)

Support Me: Support me on Patreon at Minouris Does Stuff! I'll post news and previews of all my projects, software and otherwise, including this one!

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

Copyright (c) 2025 Ciara Norrish (@minouris)

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spafw37-1.1.0.dev16.tar.gz (156.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spafw37-1.1.0.dev16-py3-none-any.whl (67.6 kB view details)

Uploaded Python 3

File details

Details for the file spafw37-1.1.0.dev16.tar.gz.

File metadata

  • Download URL: spafw37-1.1.0.dev16.tar.gz
  • Upload date:
  • Size: 156.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for spafw37-1.1.0.dev16.tar.gz
Algorithm Hash digest
SHA256 878552b545cf11271c2e8310407fa37f5c8b633c793e333a9094e414f6180584
MD5 b223e6d1b464ed26f7457e5789c39225
BLAKE2b-256 0a73ac5fe064181bfeaf34670a7f5d4e16d8f74f11174e9ba56c8cb26ecfc0e3

See more details on using hashes here.

Provenance

The following attestation bundles were made for spafw37-1.1.0.dev16.tar.gz:

Publisher: publish-stable.yml on minouris/spafw37

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file spafw37-1.1.0.dev16-py3-none-any.whl.

File metadata

  • Download URL: spafw37-1.1.0.dev16-py3-none-any.whl
  • Upload date:
  • Size: 67.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for spafw37-1.1.0.dev16-py3-none-any.whl
Algorithm Hash digest
SHA256 069cfd1c9c52efcf399c569a714fd0032da2d30739e59d1ab2276b2626ce475d
MD5 10d1dddf1350ae185a2a099ca7dc1a56
BLAKE2b-256 3c1eea201a98cbf056ff8ee7e599d3764ef64542439e9c54516b7905df71b857

See more details on using hashes here.

Provenance

The following attestation bundles were made for spafw37-1.1.0.dev16-py3-none-any.whl:

Publisher: publish-stable.yml on minouris/spafw37

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page