Validation helpers for plugin configuration dataclasses
Schema Metadata Constants
Constants for field metadata keys used in dataclass configuration. These enable validation and are compatible with JSON schema generation for UI form builders.
Field Validation
Functions for validating field values against metadata constraints.
validate_field_value
def validate_field_value( value:Any, # Value to validate metadata:Dict, # Field metadata containing constraints field_name:str='', # Field name for error messages)->Tuple: # (is_valid, error_message)
Validate a value against field metadata constraints.
Validate all fields in a configuration dataclass against their metadata constraints.
Dataclass Configuration Utilities
These functions provide utilities for working with dataclass-based plugin configurations.
config_to_dict
def config_to_dict( config:Any, # Configuration dataclass instance)->Dict: # Dictionary representation of the configuration
Convert a configuration dataclass instance to a dictionary.
Converts a dataclass configuration instance to a dictionary for serialization or passing to other systems. Also accepts dict input for passthrough convenience.
dict_to_config
def dict_to_config( config_class:Type, # Configuration dataclass type data:Optional=None, # Dictionary with configuration values validate:bool=False, # Whether to validate against metadata constraints strict:bool=True, # SG-8: reject unknown keys (default); set False to log+filter for forward-compat)->T: # Instance of the configuration dataclass
Create a configuration dataclass instance from a dictionary.
SG-8: by default, unknown keys raise PluginConfigError. The previous behavior (silently filter unknowns) is available via strict=False, which logs a warning so the drift is still visible in operator logs.
extract_defaults
def extract_defaults( config_class:Type, # Configuration dataclass type)->Dict: # Default values from the dataclass
Extract default values from a configuration dataclass type.
JSON Schema Conversion
Functions for converting dataclass configurations to JSON Schema format, enabling automatic form generation in UIs.
dataclass_to_jsonschema
def dataclass_to_jsonschema( cls:type, # Dataclass with field metadata)->Dict: # JSON schema dictionary
Convert a dataclass to a JSON schema for form generation.
# Test dataclass_to_jsonschema using ExampleConfig defined below# (This test runs after the ExampleConfig is defined in the notebook)def _test_jsonschema():"""Test JSON schema generation with ExampleConfig.""" schema = dataclass_to_jsonschema(ExampleConfig)# Check structureassert schema["name"] =="ExampleConfig"assert schema["type"] =="object"assert"properties"in schema# Check field propertiesassert schema["properties"]["model"]["type"] =="string"assert schema["properties"]["model"]["title"] =="Model"assert schema["properties"]["model"]["enum"] == ["tiny", "base", "small", "medium", "large"]assert schema["properties"]["model"]["default"] =="base"assert schema["properties"]["temperature"]["type"] =="number"assert schema["properties"]["temperature"]["minimum"] ==0.0assert schema["properties"]["temperature"]["maximum"] ==1.0assert schema["properties"]["batch_size"]["type"] =="integer"assert schema["properties"]["enabled"]["type"] =="boolean"assert schema["properties"]["tags"]["type"] =="array"print("dataclass_to_jsonschema tests passed")return schema
Example: Working with Configuration Dataclasses
from dataclasses import dataclass, fieldfrom typing import List@dataclassclass ExampleConfig:"""Example configuration dataclass with metadata constraints.""" model:str= field( default="base", metadata={ SCHEMA_TITLE: "Model", SCHEMA_DESC: "Model size to use", SCHEMA_ENUM: ["tiny", "base", "small", "medium", "large"] } ) temperature:float= field( default=0.0, metadata={ SCHEMA_TITLE: "Temperature", SCHEMA_DESC: "Sampling temperature", SCHEMA_MIN: 0.0, SCHEMA_MAX: 1.0 } ) batch_size:int= field( default=8, metadata={ SCHEMA_TITLE: "Batch Size", SCHEMA_DESC: "Batch size for processing", SCHEMA_MIN: 1, SCHEMA_MAX: 32 } ) enabled:bool= field( default=True, metadata={SCHEMA_TITLE: "Enabled", SCHEMA_DESC: "Whether feature is enabled"} ) tags:List[str] = field( default_factory=list, metadata={SCHEMA_TITLE: "Tags", SCHEMA_DESC: "Optional tags"} )print("ExampleConfig dataclass defined with metadata constraints")print(f"Fields: {[f.name for f in fields(ExampleConfig)]}")
ExampleConfig dataclass defined with metadata constraints
Fields: ['model', 'temperature', 'batch_size', 'enabled', 'tags']
# Test extract_defaultsdefaults = extract_defaults(ExampleConfig)print("Default values extracted from ExampleConfig:")for k, v in defaults.items():print(f" {k}: {v!r}")
# Test dict_to_config with validationprint("Creating config from dictionary:")# Valid configconfig1 = dict_to_config(ExampleConfig, {"model": "large", "temperature": 0.7}, validate=True)print(f"Valid config: {config1}")# Config with defaults (all valid)config2 = dict_to_config(ExampleConfig, {}, validate=True)print(f"Config with defaults: {config2}")# Test validation failure - invalid enumtry: config_bad = dict_to_config(ExampleConfig, {"model": "invalid"}, validate=True)exceptValueErroras e:print(f"\n✓ Caught invalid enum: {e}")# Test validation failure - value below minimumtry: config_bad = dict_to_config(ExampleConfig, {"temperature": -0.5}, validate=True)exceptValueErroras e:print(f"✓ Caught below minimum: {e}")# Test validation failure - value above maximumtry: config_bad = dict_to_config(ExampleConfig, {"batch_size": 100}, validate=True)exceptValueErroras e:print(f"✓ Caught above maximum: {e}")
Creating config from dictionary:
Valid config: ExampleConfig(model='large', temperature=0.7, batch_size=8, enabled=True, tags=[])
Config with defaults: ExampleConfig(model='base', temperature=0.0, batch_size=8, enabled=True, tags=[])
✓ Caught invalid enum: model: 'invalid' is not one of ['tiny', 'base', 'small', 'medium', 'large']
✓ Caught below minimum: temperature: -0.5 is less than minimum 0.0
✓ Caught above maximum: batch_size: 100 is greater than maximum 32
# SG-8 regression: dict_to_config strict mode rejects unknown keys by default;# lenient mode logs a warning and falls through to the historical filter behavior.# CR-5 update: error attribute renamed to `fields_invalid` (canonical# PluginInputError name); `unknown_keys` remains as a read-only property alias.import logging as _logging# Default-strict: an unknown key (e.g., a renamed field, typo, or stale legacy# config) now raises PluginConfigError instead of being silently dropped.try: dict_to_config(ExampleConfig, {"model": "base", "renamed_key": 42})except PluginConfigError as e:print(f"✓ strict mode rejected: fields_invalid={e.fields_invalid}, class={e.config_class_name}")assert e.fields_invalid == ["renamed_key"]assert e.config_class_name =="ExampleConfig"# SG-8-era `unknown_keys` attribute still works as a read-only alias.assert e.unknown_keys == ["renamed_key"]# Lenient mode logs the unknown key + filters it out (forward-compat path).class _CaptureHandler(_logging.Handler):def__init__(self):super().__init__()self.records = []def emit(self, record):self.records.append(record)capture = _CaptureHandler()_logger.addHandler(capture)try: cfg = dict_to_config(ExampleConfig, {"model": "tiny", "renamed_key": 42}, strict=False)assert cfg.model =="tiny"assertnothasattr(cfg, "renamed_key")assertany("renamed_key"in r.getMessage() for r in capture.records), \"lenient mode should emit a warning naming the dropped key"print("✓ lenient mode warned + filtered:", [r.getMessage() for r in capture.records])finally: _logger.removeHandler(capture)# PluginConfigError remains catchable as ValueError via PluginInputError's# multi-inheritance MRO — preserved by CR-5 for SG-8-era catch sites.try: dict_to_config(ExampleConfig, {"renamed_key": 42})exceptValueError:print("✓ PluginConfigError caught as ValueError (CR-5 MRO preserves SG-8 compat)")