Common utilities and base classes for dataknobs packages
Project description
dataknobs-common
Common utilities and base classes for all dataknobs packages.
Installation
pip install dataknobs-common
Overview
This package provides shared cross-cutting functionality used across all dataknobs packages:
- Exception Framework: Unified exception hierarchy with context support
- Registry Pattern: Generic registries for managing named items
- Serialization Protocol: Standard interfaces for to_dict/from_dict patterns
These patterns were extracted from common implementations across multiple packages to reduce duplication and provide consistency.
Features
1. Exception Framework
A unified exception hierarchy that all dataknobs packages extend. Supports both simple exceptions and context-rich exceptions with detailed error information.
Basic Usage
from dataknobs_common import DataknobsError, ValidationError, NotFoundError
# Simple exception
raise ValidationError("Invalid email format")
# Context-rich exception
raise NotFoundError(
"User not found",
context={"user_id": "123", "searched_in": "users_table"}
)
# Catch any dataknobs error
try:
operation()
except DataknobsError as e:
print(f"Error: {e}")
if e.context:
print(f"Context: {e.context}")
Available Exception Types
DataknobsError- Base exception for all packagesValidationError- Data validation failuresConfigurationError- Configuration issuesResourceError- Resource acquisition/management failuresNotFoundError- Item lookup failuresOperationError- General operation failuresConcurrencyError- Concurrent operation conflictsSerializationError- Serialization/deserialization failuresTimeoutError- Operation timeout errors
Package-Specific Extensions
from dataknobs_common import DataknobsError
class MyPackageError(DataknobsError):
"""Base exception for mypackage."""
pass
class SpecificError(MyPackageError):
"""Specific error with custom context."""
def __init__(self, item_id: str, message: str):
super().__init__(
f"Item '{item_id}': {message}",
context={"item_id": item_id}
)
2. Registry Pattern
Generic, thread-safe registries for managing collections of named items. Includes variants for caching and async support.
Basic Registry
from dataknobs_common import Registry
# Create a registry for tools
registry = Registry[Tool]("tools")
# Register items
registry.register("calculator", calculator_tool)
registry.register("search", search_tool, metadata={"version": "1.0"})
# Retrieve items
tool = registry.get("calculator")
# Check existence
if registry.has("search"):
print("Search tool available")
# List all items
for key, tool in registry.items():
print(f"{key}: {tool}")
# Get count
print(f"Registry has {registry.count()} tools")
Cached Registry
For items that should be cached with automatic TTL-based expiration:
from dataknobs_common import CachedRegistry
# Create registry with 5-minute cache
registry = CachedRegistry[Bot]("bots", cache_ttl=300)
# Get or create with factory
bot = registry.get_cached(
"client1",
factory=lambda: create_bot("client1")
)
# Get cache statistics
stats = registry.get_cache_stats()
print(f"Hit rate: {stats['hit_rate']:.2%}")
# Invalidate cache
registry.invalidate_cache("client1") # Single item
registry.invalidate_cache() # All items
Async Registry
For async contexts:
from dataknobs_common import AsyncRegistry
registry = AsyncRegistry[Resource]("resources")
# All operations are async
await registry.register("db", db_resource)
resource = await registry.get("db")
count = await registry.count()
Plugin Registry
For managing plugins with factory support, defaults, and lazy instantiation:
from dataknobs_common import PluginRegistry
# Define a base class
class Handler:
def __init__(self, name: str, config: dict):
self.name = name
self.config = config
class DefaultHandler(Handler):
pass
class CustomHandler(Handler):
pass
# Create registry with default factory
registry = PluginRegistry[Handler]("handlers", default_factory=DefaultHandler)
# Register plugins
registry.register("custom", CustomHandler)
# Get instances (lazy creation with caching)
handler = registry.get("custom", config={"timeout": 30})
default = registry.get("unknown", config={}) # Uses default
# Async factory support
async def create_async_handler(name, config):
handler = AsyncHandler(name, config)
await handler.initialize()
return handler
registry.register("async", create_async_handler)
handler = await registry.get_async("async", config={"url": "..."})
PluginRegistry Features
# Bulk registration
registry.bulk_register({
"handler1": Handler1,
"handler2": Handler2,
})
# Check registration
if registry.is_registered("custom"):
print("Custom handler available")
# List all registered plugins
keys = registry.list_keys()
# Clear cached instances
registry.clear_cache("custom") # Single
registry.clear_cache() # All
# Get factory without creating instance
factory = registry.get_factory("custom")
# Set default after init
registry.set_default_factory(NewDefault)
Custom Registry Extensions
from dataknobs_common import Registry
class ToolRegistry(Registry[Tool]):
"""Registry for LLM tools."""
def __init__(self):
super().__init__("tools", enable_metrics=True)
def register_tool(self, tool: Tool) -> None:
"""Register a tool with metadata."""
self.register(
tool.name,
tool,
metadata={"description": tool.description}
)
def get_by_category(self, category: str) -> list[Tool]:
"""Get all tools in a category."""
return [
tool for tool in self.list_items()
if tool.category == category
]
3. Serialization Protocol
Standard protocol for objects that can be serialized to/from dictionaries.
Define Serializable Classes
from dataknobs_common import Serializable
from dataclasses import dataclass
@dataclass
class User:
name: str
email: str
def to_dict(self) -> dict:
return {"name": self.name, "email": self.email}
@classmethod
def from_dict(cls, data: dict) -> "User":
return cls(name=data["name"], email=data["email"])
# Type checking works
user = User("Alice", "alice@example.com")
assert isinstance(user, Serializable) # True
Serialization Utilities
from dataknobs_common import serialize, deserialize, serialize_list, deserialize_list
# Serialize single object
user = User("Alice", "alice@example.com")
data = serialize(user)
# {'name': 'Alice', 'email': 'alice@example.com'}
# Deserialize
restored_user = deserialize(User, data)
# Serialize list
users = [User("Alice", "a@ex.com"), User("Bob", "b@ex.com")]
data_list = serialize_list(users)
# Deserialize list
restored_users = deserialize_list(User, data_list)
Type Checking
from dataknobs_common import is_serializable, is_deserializable
# Check if object can be serialized
if is_serializable(my_object):
data = serialize(my_object)
# Check if class can deserialize
if is_deserializable(MyClass):
obj = deserialize(MyClass, data)
Integration with Dataknobs Packages
This package is designed to be imported and extended by all dataknobs packages:
# In dataknobs_data package
from dataknobs_common import DataknobsError, NotFoundError
class DataknobsDataError(DataknobsError):
"""Base exception for data package."""
pass
class RecordNotFoundError(NotFoundError):
"""Record not found in database."""
pass
# In dataknobs_llm package
from dataknobs_common import Registry
class ToolRegistry(Registry[Tool]):
"""Registry for LLM tools."""
def to_function_definitions(self) -> list[dict]:
"""Convert tools to function definitions."""
return [tool.to_function_definition() for tool in self.list_items()]
Migration Guide
For Package Developers
If your package has custom exceptions, consider extending from dataknobs_common:
Before:
# packages/mypackage/exceptions.py
class MyPackageError(Exception):
pass
After:
# packages/mypackage/exceptions.py
from dataknobs_common import DataknobsError
class MyPackageError(DataknobsError):
pass
For Application Developers
You can now catch all dataknobs exceptions uniformly:
from dataknobs_common import DataknobsError
try:
# Use any dataknobs package
result = database.query()
llm.complete()
bot.chat()
except DataknobsError as e:
logger.error(f"Dataknobs error: {e}")
if e.context:
logger.error(f"Context: {e.context}")
Why Common Package?
After building out core packages (data, llm, fsm, bots), we identified several patterns that were:
- Implemented multiple times - Exception hierarchies in 4+ packages
- Highly similar - Registry pattern ~100-150 lines duplicated 3 times
- Genuinely cross-cutting - Used across domain boundaries
Extracting these patterns to dataknobs-common provides:
- Reduced duplication - Single source of truth
- Consistency - Same patterns everywhere
- Type safety - Shared protocols and interfaces
- Better error handling - Unified exception catching
- Ecosystem coherence - Packages feel integrated
API Reference
See module docstrings for detailed API documentation:
dataknobs_common.exceptions- Exception hierarchydataknobs_common.registry- Registry implementationsdataknobs_common.serialization- Serialization protocols
Dependencies
- Python 3.10+
- No external dependencies (uses only standard library)
License
See LICENSE file in the root repository.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dataknobs_common-1.3.9.tar.gz.
File metadata
- Download URL: dataknobs_common-1.3.9.tar.gz
- Upload date:
- Size: 169.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c3e53c3d799b91eb91489372bb6e8dd3d8b6905c5605e83417046ea200a1f755
|
|
| MD5 |
f84b1ecd13070fd028b0f64be03d98af
|
|
| BLAKE2b-256 |
1b957e2a414e01e7123544e64089588b8ae0b922e9b8160eaa4cebe262711718
|
File details
Details for the file dataknobs_common-1.3.9-py3-none-any.whl.
File metadata
- Download URL: dataknobs_common-1.3.9-py3-none-any.whl
- Upload date:
- Size: 58.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d9be59b2ed8edcc63715686167b0f284a4523c2c428180664cec52eb78df1f2
|
|
| MD5 |
2da319ba6cb4453d798849380f78ca7c
|
|
| BLAKE2b-256 |
cc14982b4033c37e04f67c7cb3ffae1034ef2504d9582073631be10d0930ba07
|