Usage Guide
This guide walks through the primary features of ConfigGuard using examples.
Core Concepts Recap
Before diving in, remember these key ideas:
Schema: A Python dictionary defining your settings (types, defaults, validation, version).
ConfigGuard Instance: The main object holding the schema and current values.
Handlers: Internal components for reading/writing different file formats (e.g., JSON) and handling encryption.
Save Modes:
mode='values'
(default, saves only values) ormode='full'
(saves version, schema, and values).Versioning: Automatic comparison during
load()
; handles older versions via migration, raises errors for newer versions.
1. Defining Your Schema
Create a Python dictionary. The top level needs __version__
. Other keys are your settings.
generate_encryption_key,
log,
set_log_level,
)
def run_basic_usage_example() -> None:
"""Executes the main demonstration of ConfigGuard features."""
# Set log level for more verbose output during example run
set_log_level("DEBUG")
log.info("Starting ConfigGuard Basic Usage Example (with Nesting)...")
# --- Configuration Constants ---
CONFIG_VERSION = "2.0.0" # Updated version for schema with nesting
BASE_FILENAME = "my_app_config_nested"
SECRET_KEY_FILE = Path("config_nested.secret")
SCHEMA_DEFINITION_FILE = Path(
"my_app_schema_nested_definition.json"
) # For saving schema definition
# --- 1. Define Schema with Nested Sections ---
my_schema: typing.Dict[str, typing.Any] = {
"__version__": CONFIG_VERSION,
"server": {
"type": "section",
"help": "Web server configuration.",
"schema": {
"host": {
"type": "str",
"default": "127.0.0.1",
"help": "Hostname or IP address to bind the server to.",
},
"port": {
"type": "int",
"default": 8080,
"min_val": 1024,
"max_val": 65535,
"help": "The network port the application should listen on.",
},
"timeout_seconds": {
"type": "float",
"default": 30.0,
"min_val": 0.5,
"help": "Request timeout in seconds.",
},
See the schema definition keys in the main README or API docs for SettingSchema.
2. Initializing ConfigGuard
Pass the schema (dict or file path) and optionally the config file path and encryption key.
from configguard import ConfigGuard, generate_encryption_key
from pathlib import Path
# Assume my_app_schema is the dictionary defined above
# Assume config_file_path points to "my_settings.json"
# Assume full_state_file_path points to "my_settings_full.json"
# Assume enc_key is a valid Fernet key
# Basic initialization (values mode, no encryption)
config_basic = ConfigGuard(schema=my_app_schema, config_path=config_file_path)
# With encryption
config_encrypted = ConfigGuard(
schema=my_app_schema,
config_path="encrypted_settings.bin", # Use appropriate extension
encryption_key=enc_key
)
# With autosave (saves values automatically on change)
# config_autosave = ConfigGuard(schema=my_app_schema, config_path=config_file_path, autosave=True)
ConfigGuard automatically tries to load()
from config_path
if it exists.
3. Accessing Settings and Schema
Use attribute or dictionary syntax. Use the sc_
prefix for schema details.
# Access value
port = config_basic.port
log_level = config_basic['log_level']
# Access schema
port_help = config_basic.sc_port.help
is_db_nullable = config_basic['sc_database_uri'].nullable
print(f"Port: {port} (Help: {port_help})")
print(f"Is DB Nullable? {is_db_nullable}")
4. Modifying Settings
Assign values directly. Validation occurs automatically.
config_basic.port = 8443
config_basic['log_level'] = 'WARNING'
config_basic.feature_flags.append('new_feature_x') # Modifying lists works directly
try:
config_basic.port = 100 # Below min_val
except ValidationError as e:
print(f"Validation failed: {e}")
5. Saving Configuration
Specify the mode
: 'values'
or 'full'
.
# Save only values to the default path
config_basic.save(mode='values')
print(f"Values saved to {config_basic._config_path}") # Access internal for demo
# Save full state to a different file
config_basic.save(filepath="config_backup.json", mode='full')
print("Full state saved to config_backup.json")
6. Loading and Version Handling
Loading usually happens on initialization. Manual config.load()
is possible. Versioning is automatic.
Let’s simulate loading an older version:
try:
config.logging.level = "TRACE" # Not in options
except ValidationError as e:
log.info(f"Caught expected validation error: {e}")
try:
config.server.enabled = "yes" # Invalid type for bool
except ValidationError as e:
log.info(f"Caught expected validation error: {e}")
try:
config.database.uri = None # Allowed due to nullable=True
log.info(
f"Successfully set database.uri back to None. Value: {config.database.uri}"
)
except ValidationError as e:
log.error(
f"Caught UNEXPECTED validation error setting nullable field to None: {e}"
)
# Reset database_uri for subsequent steps
config.database.uri = "sqlite:///prod_nested.db"
# --- 7. Saving Configuration (Values vs Full - Nested) ---
log.info("\n--- Saving Configuration (Nested) ---")
# Save only values to the default path
log.info(f"Saving mode='values' to {config_file_path}...")
config.save(mode="values")
log.info(f"Content of {config_file_path} (values only - nested):")
log.info(
# Assume my_app_schema is the V1.1.0 schema
# Assume older_file path points to the simulated V1.0.0 file
try:
print("\\nLoading older config into V1.1.0 instance...")
# Initialize with CURRENT schema, load OLD file
config_migrated = ConfigGuard(schema=my_app_schema, config_path=older_file)
print(f"Loaded file version: {config_migrated.loaded_file_version}") # Should be "1.0.0"
print(f"Instance version: {config_migrated.version}") # Should be "1.1.0"
# Values from old file are loaded if key exists in V1.1.0 schema
print(f"Port loaded from old: {config_migrated.port}") # e.g., 7000
# New settings get V1.1.0 defaults
print(f"Timeout (new in V1.1.0): {config_migrated.timeout_seconds}") # e.g., 30.0
# Settings only in old file are skipped (warning logged)
except SchemaError as e:
print(f"Schema error (e.g., file version too new): {e}")
except Exception as e:
print(f"Other loading error: {e}")
7. Encryption
Provide a encryption_key during initialization. Saving and loading become transparently encrypted/decrypted by the handler.
older_config_full_state_structured = {
"version": older_version,
"schema": { # Pretend V1 had *some* structure
"server": {
"type": "section",
"schema": {
"port": {"type": "int", "default": 8000},
"enabled": {"type": "bool"},
},
},
"database": {
"type": "section",
"schema": {
"uri": {
"type": "str",
"nullable": False,
"default": "default.db",
},
"retry_attempts": {"type": "int"},
},
},
"logging_level": {
"type": "str",
"default": "WARN",
"options": ["INFO", "WARN"],
}, # Flat
"security_key": {
"type": "str",
"nullable": True,
}, # Flat, maps to 'security'
"removed_section": {
"type": "section",
"schema": {"old_val": {"type": "int"}},
},
},
"values": {
"server": {
"port": 7000,
"enabled": True,
},
"database": {
"uri": "old_db.sqlite",
"retry_attempts": 5,
},
"logging_level": "INFO", # Maps to logging.level
"security_key": "secret-v1.0", # Maps to security
"removed_section": {"old_val": 123},
},
8. Import/Export
Export Current State (Schema + Values)
Useful for UIs or sending state over APIs.
current_state = config_basic.export_schema_with_values()
import json
print(json.dumps(current_state, indent=2))
Import Values from Dictionary
Update settings from a dict (e.g., from a web form). Only affects values, ignores schema/version.
update_data = {"port": 9999, "log_level": "ERROR", "unknown": "skipped"}
try:
config_basic.import_config(update_data, ignore_unknown=True)
print(f"Port after import: {config_basic.port}")
except Exception as e:
print(f"Import failed: {e}")
This covers the main workflows for using ConfigGuard effectively. Check the API Reference reference for detailed class and method information.