Core Examples¶
Examples demonstrating protocols, registry patterns, and exception handling.
Overview¶
The core module provides foundational utilities including structural typing with protocols, plugin registry, and rich exception handling.
Examples¶
1. Protocols¶
File: examples/core/01_protocols_basic.py
Demonstrates structural typing with protocols.
Topics: - Serializable: Objects that can be converted to/from dictionaries - AsyncResource: Objects that support async context management - Closeable: Objects with sync and async cleanup methods
Key Concepts:
- Duck typing without inheritance
- Runtime protocol checking with isinstance()
- Flexible, testable code design
Run:
2. Registry & Plugins¶
File: examples/core/02_registry_plugins.py
Shows how to build extensible plugin systems.
Topics: - Data transformer plugins - Serializer plugins (JSON, Pickle) - Validator plugins (required fields, types, ranges) - Registry operations (register, get, has, unregister, clear)
Key Concepts: - Plugin architecture without complex frameworks - Type-safe plugin registration - Multiple plugin systems in one application
Run:
3. Exception Handling¶
File: examples/core/03_exception_handling.py
Demonstrates rich error handling with context.
Topics: - ConfigurationError: Config loading failures with suggestions - ValidationError: Data validation with field, value, and constraint info - DSPUIOError: File operations with path and operation context - SecurityError: Security violations with reason and resource info - Exception hierarchy and catching patterns - Exception chaining
Key Concepts: - Actionable error messages - Rich error context for debugging - Exception chaining to preserve information - Best practices for error handling
Run:
Quick Start¶
from dspu.core import Registry, Serializable
from typing import Protocol
# Define a protocol
class Processor(Protocol):
def process(self, data: str) -> str: ...
# Create a registry
processors = Registry[Processor]()
# Register plugins
class UpperProcessor:
def process(self, data: str) -> str:
return data.upper()
processors.register("upper", UpperProcessor())
# Use plugins
processor = processors.get("upper")
result = processor.process("hello") # "HELLO"
Common Patterns¶
Pattern 1: Plugin Architecture¶
from dspu.core import Registry
from typing import Protocol
# Define plugin interface
class DataTransformer(Protocol):
def transform(self, data: dict) -> dict: ...
# Create registry
transformers = Registry[DataTransformer]()
# Register transformers
class NormalizeKeys:
def transform(self, data: dict) -> dict:
return {k.lower(): v for k, v in data.items()}
class AddTimestamp:
def transform(self, data: dict) -> dict:
data["timestamp"] = datetime.now()
return data
transformers.register("normalize_keys", NormalizeKeys())
transformers.register("add_timestamp", AddTimestamp())
# Use transformers
data = {"Name": "Alice", "AGE": 30}
data = transformers.get("normalize_keys").transform(data)
data = transformers.get("add_timestamp").transform(data)
Pattern 2: Serialization Interface¶
from dspu.core import Serializable
class User:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def to_dict(self) -> dict:
return {"name": self.name, "age": self.age}
@classmethod
def from_dict(cls, data: dict) -> "User":
return cls(name=data["name"], age=data["age"])
# User implements Serializable protocol
user = User("Alice", 30)
data = user.to_dict()
restored = User.from_dict(data)
Pattern 3: Rich Error Messages¶
from dspu.core import ConfigurationError, ValidationError
def load_config(path: str):
if not path.exists():
raise ConfigurationError(
f"Configuration file not found: {path}",
suggestion="Create the file or check the path"
)
def validate_user(data: dict):
if "email" not in data:
raise ValidationError(
"Email is required",
field="email",
value=None,
constraint="required"
)
Pattern 4: Resource Management¶
from dspu.core import AsyncResource
class Database:
async def __aenter__(self):
await self.connect()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.disconnect()
# Database implements AsyncResource protocol
async with Database() as db:
await db.query("SELECT * FROM users")
Protocol Reference¶
Serializable¶
class Serializable(Protocol):
def to_dict(self) -> dict: ...
@classmethod
def from_dict(cls, data: dict) -> Self: ...
AsyncResource¶
class AsyncResource(Protocol):
async def __aenter__(self) -> Self: ...
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: ...
Closeable¶
Registry API¶
# Create registry
registry = Registry[MyProtocol]()
# Register items
registry.register("key", instance)
# Get items
instance = registry.get("key")
# Check existence
if registry.has("key"):
...
# List keys
keys = registry.keys()
# Unregister
registry.unregister("key")
# Clear all
registry.clear()
Exception Hierarchy¶
DSPUError (base)
├── ConfigurationError
├── ValidationError
├── DSPUIOError
│ ├── FormatError
│ └── StorageError
├── SecurityError
└── RetryError
Best Practices¶
✅ DO: - Use protocols for flexible interfaces - Provide rich error context (field, value, suggestion) - Use registry for plugin systems - Implement protocols implicitly (duck typing) - Chain exceptions to preserve context
❌ DON'T: - Don't use inheritance when protocols suffice - Don't raise generic exceptions without context - Don't forget to document protocol requirements - Don't swallow exceptions without logging