Component base class, ComponentConfig dataclass, and the patterns you should follow when extending the codebase.
Overview
Copy
Ask AI
from myspellchecker.core.component import Component, ComponentConfig
from dataclasses import dataclass
@dataclass
class MyConfig(ComponentConfig):
max_size: int = 100
threshold: float = 0.5
class MyComponent(Component[MyConfig]):
def __init__(
self,
config: MyConfig,
provider: SomeProvider,
*,
cache: Optional[Cache] = None,
):
super().__init__(config)
self.provider = provider
self.cache = cache
Design Principles
Constructor Signature Standards
All components follow this parameter ordering:Copy
Ask AI
class StandardComponent(Component[ConfigType]):
def __init__(
self,
config: ConfigType, # 1. Config FIRST
provider: DictionaryProvider, # 2. Required dependencies
segmenter: Segmenter, # 2. More required deps
*, # 3. Keyword-only separator
cache: Optional[Cache] = None,# 4. Optional with defaults
debug: bool = False, # 4. More optionals
):
super().__init__(config)
self.provider = provider
self.segmenter = segmenter
self.cache = cache
self.debug = debug
- Config first: Central configuration is always available
- Dependencies second: Required services are explicit
- Optionals last: Defaults reduce boilerplate
ComponentConfig
Base class for all configuration objects:Copy
Ask AI
from dataclasses import dataclass
from typing import Any, Dict
@dataclass
class ComponentConfig:
"""Base configuration class for components."""
name: str = "" # Optional component name
enabled: bool = True # Enable/disable flag
def validate(self) -> bool:
"""Validate the configuration.
Override to add custom validation logic.
Returns:
True if configuration is valid.
"""
return True
def to_dict(self) -> Dict[str, Any]:
"""Convert configuration to dictionary."""
from dataclasses import asdict
return asdict(self)
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "ComponentConfig":
"""Create configuration from dictionary."""
valid_fields = {f.name for f in cls.__dataclass_fields__.values()}
filtered = {k: v for k, v in data.items() if k in valid_fields}
return cls(**filtered)
Creating Custom Configs
Copy
Ask AI
from dataclasses import dataclass
from myspellchecker.core.component import ComponentConfig
@dataclass
class SymSpellConfig(ComponentConfig):
"""Configuration for SymSpell algorithm."""
prefix_length: int = 10
max_edit_distance: int = 2
beam_width: int = 50
def validate(self) -> bool:
"""Validate SymSpell parameters."""
if self.prefix_length < 1:
return False
if self.max_edit_distance < 0:
return False
if self.beam_width < 1:
return False
return True
@dataclass
class ValidatorConfig(ComponentConfig):
"""Configuration for text validators."""
strict_mode: bool = False
max_word_length: int = 50
min_frequency: int = 1
def validate(self) -> bool:
return self.max_word_length > 0
Component Base Class
Generic base class for configurable components:Copy
Ask AI
from abc import ABC
from typing import Generic, TypeVar, Any, Dict
import logging
C = TypeVar("C", bound=ComponentConfig)
class Component(ABC, Generic[C]):
"""Base class for configurable components.
Provides:
- Configuration object as first parameter
- Automatic logger setup
- Configuration validation
- Common metadata handling
"""
def __init__(self, config: C) -> None:
self._config = config
self._logger = get_logger(self.__class__.__name__)
self._metadata: Dict[str, Any] = {}
# Validate configuration
if hasattr(config, "validate") and not config.validate():
self._logger.warning(
f"Configuration validation failed for {self.__class__.__name__}"
)
@property
def config(self) -> C:
"""Return the component configuration."""
return self._config
@property
def logger(self) -> logging.Logger:
"""Return the component logger."""
return self._logger
@property
def metadata(self) -> Dict[str, Any]:
"""Return component metadata."""
return self._metadata
def reconfigure(self, config: C) -> None:
"""Update the component configuration."""
self._config = config
self._logger.debug(f"Reconfigured {self.__class__.__name__}")
Implementing Components
Copy
Ask AI
from myspellchecker.core.component import Component, ComponentConfig
from dataclasses import dataclass
@dataclass
class NgramCheckerConfig(ComponentConfig):
use_bigrams: bool = True
use_trigrams: bool = True
smoothing: str = "kneser_ney"
threshold: float = 0.001
class NgramChecker(Component[NgramCheckerConfig]):
"""N-gram based context checker."""
def __init__(
self,
config: NgramCheckerConfig,
provider: DictionaryProvider,
*,
cache: Optional[LRUCache] = None,
):
super().__init__(config)
self.provider = provider
self.cache = cache
# Initialize based on config
self._init_ngram_tables()
def _init_ngram_tables(self) -> None:
"""Initialize n-gram data based on config."""
if self.config.use_bigrams:
self.logger.debug("Loading bigram data")
self._bigrams = self.provider.get_pos_bigram_probabilities()
if self.config.use_trigrams:
self.logger.debug("Loading trigram data")
self._trigrams = self.provider.get_pos_trigram_probabilities()
def check_context(self, words: List[str]) -> float:
"""Check context probability."""
# Implementation using config values
...
Configurable Protocol
Protocol for components that support reconfiguration:Copy
Ask AI
from typing import Protocol, TypeVar, runtime_checkable
C = TypeVar("C", bound=ComponentConfig)
@runtime_checkable
class Configurable(Protocol[C]):
"""Protocol for configurable components."""
@property
def config(self) -> C:
"""Return the component configuration."""
...
def reconfigure(self, config: C) -> None:
"""Update the component configuration."""
...
# Usage
def update_config(component: Configurable[SomeConfig], new_config: SomeConfig):
component.reconfigure(new_config)
ComponentMeta
Metadata class for component introspection:Copy
Ask AI
from dataclasses import dataclass, field
from typing import Optional, List
@dataclass
class ComponentMeta:
"""Metadata about a component.
Used for introspection, documentation, and factory registration.
"""
name: str
description: str = ""
version: str = "1.0.0"
author: str = ""
config_class: Optional[type] = None
dependencies: List[str] = field(default_factory=list)
optional_dependencies: List[str] = field(default_factory=list)
tags: List[str] = field(default_factory=list)
# Example usage
SYMSPELL_META = ComponentMeta(
name="SymSpell",
description="O(1) spelling suggestion algorithm",
version="1.0.0",
config_class=SymSpellConfig,
dependencies=["DictionaryProvider"],
optional_dependencies=["LRUCache"],
tags=["algorithm", "suggestions"],
)
with_config Decorator
Associates configuration class with component:Copy
Ask AI
def with_config(config_class: type) -> type:
"""Class decorator to associate a configuration class.
Adds metadata about the configuration class
to the component for introspection and factory use.
"""
def decorator(cls: type) -> type:
cls._config_class = config_class
return cls
return decorator
# Usage
@with_config(SymSpellConfig)
class SymSpell(Component[SymSpellConfig]):
pass
# Access config class
config_cls = SymSpell._config_class # SymSpellConfig
Best Practices
1. Always Use Dataclass Configs
Copy
Ask AI
# Good: Dataclass config
@dataclass
class MyConfig(ComponentConfig):
option1: int = 10
option2: str = "default"
# Bad: Plain dict
config = {"option1": 10, "option2": "default"}
2. Validate Configuration
Copy
Ask AI
@dataclass
class ValidatedConfig(ComponentConfig):
port: int = 8080
host: str = "localhost"
def validate(self) -> bool:
if not 1 <= self.port <= 65535:
return False
if not self.host:
return False
return True
3. Use Type Hints
Copy
Ask AI
# Good: Explicit types
class MyComponent(Component[MyConfig]):
def __init__(
self,
config: MyConfig,
provider: DictionaryProvider,
) -> None:
super().__init__(config)
# Bad: No type hints
class MyComponent:
def __init__(self, config, provider):
...
4. Document Dependencies
Copy
Ask AI
class DocumentedComponent(Component[MyConfig]):
"""Component with clear dependency documentation.
Args:
config: Component configuration
provider: Dictionary provider for data access
cache: Optional LRU cache for performance
Requires:
- DictionaryProvider with word lookup support
- Optional: LRUCache for suggestion caching
"""
5. Use Logger Mixin
Copy
Ask AI
class MyComponent(Component[MyConfig]):
def some_method(self):
self.logger.debug("Starting operation")
try:
result = self._do_work()
self.logger.info(f"Completed: {result}")
except Exception as e:
self.logger.error(f"Failed: {e}")
raise
Integration Example
Complete example showing all patterns:Copy
Ask AI
from dataclasses import dataclass
from typing import Optional, List
from myspellchecker.core.component import Component, ComponentConfig, with_config
@dataclass
class SpellCheckerConfig(ComponentConfig):
"""Main spell checker configuration."""
validation_level: str = "word"
use_context_checker: bool = True
cache_size: int = 10000
def validate(self) -> bool:
valid_levels = {"syllable", "word"}
return self.validation_level in valid_levels
# Note: This is a conceptual example showing the Component pattern.
# The actual SpellChecker does NOT inherit from Component; it uses
# its own constructor: __init__(config, segmenter, provider,
# syllable_validator, word_validator, context_validator, factory)
class ExampleComponent(Component[SpellCheckerConfig]):
"""Example showing Component pattern (conceptual, not actual SpellChecker)."""
def __init__(
self,
config: SpellCheckerConfig,
provider: DictionaryProvider,
*,
segmenter: Optional[Segmenter] = None,
):
super().__init__(config)
self.provider = provider
self.segmenter = segmenter or DefaultSegmenter()
self.logger.info(f"Initialized with level: {config.validation_level}")
def check(self, text: str) -> Response:
"""Check text for spelling errors."""
self.logger.debug(f"Checking text: {text[:50]}...")
# Implementation
...
See Also
- Dependency Injection - DI system
- Configuration Guide - Config management
- Logging Guide - Logging patterns
- Algorithm Factory - Component creation