Skip to content

ConfigModel (configuration shells)

PICLE ships a helper base model for “configuration-mode” shells: picle.models.ConfigModel. It implements a common workflow:

  • Load configuration from YAML (optional dependency: pyyaml)
  • Stage edits into a temporary file (<config_file>.tmp)
  • Review staged changes (show changes) and commit them (commit)
  • Keep rotating backups on commit (.old1, .old2, ...)
  • Roll back by loading a backup into the temp file (rollback <n>) and then committing

ConfigModel.PicleConfig keys

ConfigModel reads additional settings from the concrete model’s PicleConfig. These keys are only honored by ConfigModel (the core App ignores them):

Name Meaning
config_file Path to the YAML config file (default: configuration.yaml)
backup_on_save How many .oldN backups to keep when committing (0 disables backups)
commit_hook Optional callable invoked after a successful commit

Sample Config Model Shell

Below is example of how to use configuration model to construct interactive shell to manage YAML file content.

"""
Example demonstrating ConfigModel usage in PICLE shells.

This example shows how to create a configuration management system using
ConfigModel to handle YAML configuration files with structured validation.
"""

from enum import Enum
from typing import Optional, Dict
from pydantic import BaseModel, Field, ConfigDict, StrictStr

from picle.models import ConfigModel, PipeFunctionsModel
from picle.picle import App

# --------------------------------------------------------------------------------
# Configuration Structure Models
# --------------------------------------------------------------------------------


class SeverityEnum(str, Enum):
    """Logging severity levels."""

    debug = "debug"
    info = "info"
    warning = "warning"
    error = "error"
    critical = "critical"


class TerminalLoggingConfig(BaseModel):
    """Terminal logging configuration."""

    severity: SeverityEnum = Field(
        SeverityEnum.info, description="Logging severity level"
    )
    format: str = Field(None, description="Log message format", alias="format")


class FileLoggingConfig(BaseModel):
    """File logging configuration."""

    enabled: bool = Field(False, description="Enable file logging")
    path: str = Field(None, description="Log file path")
    severity: SeverityEnum = Field(
        SeverityEnum.warning, description="File logging severity level"
    )


class LoggingConfigModel(BaseModel):
    """Main logging configuration."""

    terminal: TerminalLoggingConfig = Field(
        None, description="Terminal logging configuration"
    )
    file: FileLoggingConfig = Field(None, description="File logging configuration")

class WorkerConfigModel(BaseModel):
    timeout: int = Field(None, description="Worker timeout in seconds")
    num_threads: int = Field(None, description="Number of worker threads")
    use_chache: bool = Field(None, description="Whether to use cache for worker results")


# --------------------------------------------------------------------------------
# Configuration Store with Commands
# --------------------------------------------------------------------------------


class MyConfigStore(ConfigModel):
    """
    Configuration store for application settings.

    This model manages YAML configuration files and provides commands
    to view, get, and set configuration values.
    """

    # Configuration structure definition
    logging: LoggingConfigModel = Field(None, description="Logging configuration")
    workers: dict[StrictStr, WorkerConfigModel] = Field(
        None, description="Worker configurations", json_schema_extra={"pkey": "worker_name", "pkey_description": "Name of the worker"}
    )
    class PicleConfig:
        subshell = True
        prompt = "config-shell[cfg]#"
        config_file = "app_config.yaml"  # Default config file path


# --------------------------------------------------------------------------------
# Root Shell Model
# --------------------------------------------------------------------------------


class RootShell(BaseModel):
    """Root shell with config command."""

    configure_terminal: MyConfigStore = Field(
        None, description="Configuration management commands"
    )

    class PicleConfig:
        pipe = PipeFunctionsModel
        prompt = "config-shell#"


# --------------------------------------------------------------------------------
# Example Usage
# --------------------------------------------------------------------------------


if __name__ == "__main__":
    shell = App(RootShell)
    shell.start()

Above app constructs shell with this commands tree:

config-shell#man tree configure_terminal

R - required field, M - supports multiline input, D - dynamic key

root
└── configure_terminal:    Configuration management commands
    ├── show:    Show commands
    │   ├── configuration:    Show running configuration content
    │   └── changes:    Show uncommitted changes diff between temp and running config
    ├── commit:    Commit pending config changes
    ├── rollback:    Rollback to a backup version
    ├── erase-configuration:    Erase running configuration
    ├── clear-changes:    Discard uncommitted changes
    ├── logging:    Logging configuration
    │   ├── terminal:    Terminal logging configuration
    │   │   ├── severity:    Logging severity level, default 'SeverityEnum.info'
    │   │   └── format:    Log message format
    │   └── file:    File logging configuration
    │       ├── enabled:    Enable file logging, default 'False'
    │       ├── path:    Log file path
    │       └── severity:    File logging severity level, default 'SeverityEnum.warning'
    └── workers:    Worker configurations
        ├── worker_name (D):    Name of the worker
        ├── timeout:    Worker timeout in seconds
        ├── num_threads:    Number of worker threads
        └── use_cache:    Whether to use cache for worker results
config-shell#

And above shell can be used like this:

config-shell#configure_terminal
config-shell[cfg]#workers ?
 <worker_name>    Name of the worker
config-shell[cfg]#workers worker-1 ?
 num_threads    Number of worker threads
 timeout        Worker timeout in seconds
 use_cache      Whether to use cache for worker results
config-shell[cfg]#workers worker-1 num_threads 1 timeout 1 use_cache True
Configuration updated (uncommitted). Use 'commit' to save or 'show changes' to review.
config-shell[cfg]#show changes
--- app_config.yaml
+++ app_config.yaml.tmp
@@ -1 +1,6 @@
-{}
+workers:
+  worker-1:
+    num_threads: 1
+    timeout: 1
+    use_cache: true
+    worker_name: worker-1
config-shell[cfg]#commit
Configuration committed successfully
config-shell[cfg]#exit
config-shell#

Negating configuration — the no command

Every ConfigModel subclass automatically gains a no command that mirrors the full configuration field tree. It provides the same tab-completion as regular set commands and lets you delete individual keys or entire sub-trees from the staged configuration.

Changes made with no are written to the temp file (same as regular set commands) and only become permanent after commit.

Deleting a specific leaf field

Name one or more leaf fields after the path to remove those keys only:

config-shell[cfg]#no workers worker-1 timeout
Configuration negated (uncommitted). Use 'commit' to save or 'show changes' to review.
config-shell[cfg]#show changes
--- app_config.yaml
+++ app_config.yaml.tmp
@@ -1,6 +1,5 @@
 workers:
   worker-1:
     num_threads: 1
-    timeout: 1
     use_cache: true
     worker_name: worker-1
config-shell[cfg]#commit
Configuration committed successfully

Multiple leaf fields can be removed in a single command:

config-shell[cfg]#no workers worker-1 timeout num_threads

Deleting an entire sub-tree

Omit leaf fields and the whole node is removed:

config-shell[cfg]#no workers worker-1
Configuration negated (uncommitted). Use 'commit' to save or 'show changes' to review.
config-shell[cfg]#show changes
--- app_config.yaml
+++ app_config.yaml.tmp
@@ -1,6 +1 @@
-workers:
-  worker-1:
-    num_threads: 1
-    timeout: 1
-    use_cache: true
-    worker_name: worker-1
+{}
config-shell[cfg]#commit
Configuration committed successfully